The HTTP client library allows you to configure your device as a HTTP client, and enables you to send HTTP requests to an HTTP server and parse the responses received by the server.
Although the library communicates over the socket API, the application is responsible for creating a socket and passing it to the library.
Create an HTTP request of type struct http_request
, which will contain all the data necessary for doing an HTTP request.
In the code snippet below, we are creating an HTTP request and populating the essential members to be able to send a PUT request.
struct http_request req;
static uint8_t recv_buf[512];
const char *headers[] = {"Connection: close\r\n", NULL};
char * payload_buf = "payload";
req.header_fields = headers;
req.method = HTTP_PUT;
req.url = "/";
req.host = HTTP_HOSTNAME;
req.protocol = "HTTP/1.1";
req.payload = payload_buf;
req.payload_len = strlen(payload_buf);
req.response = response_cb;
req.recv_buf = recv_buf;
req.recv_buf_len = sizeof(recv_buf);
Cheader_fields
: the HTTP header fields. For example: Connection, Content-Type, Authorizationmethod:
HTTP method of the request. For example: GET, POST, PUT, etc. Defined by the enum http_method
url:
URL for the request. For example “/new”host:
Hostname to be used in the requestprotocol:
HTTP version being used, necessary for the header. For example “HTTP/1.1”payload:
Payload of HTTP requestpayload_len:
Payload length, used to calculate HTTP Content-Length headerresponse:
User supplied callback function to call when response is receivedrecv_buf:
User supplied buffer where received data is storedrecv_buf_len
: Length of recv_buf
When the HTTP request is populated, we will send it using the API call http_client_req()
.
ret = http_client_req(sock, &req, 5000, "IPv4 PUT");
Csock:
Socket file descriptor of the socket connected to the HTTP serverreq:
Pointer to the HTTP requesttimeout:
Maximum timeout to wait for datauser_data:
User specified data that is passed to the callback, can be used for instance to identify which HTTP request it is responding toWhen populating the HTTP request, we provided a callback function to be called when a response to the HTTP request is received. This callback has the following signature
rsp
: HTTP response information, of type struct http_response
final_data
: Indicates whether data bufer contains all the data, can be either HTTP_DATA_MORE
or HTTP_DATA_FINAL
user_data
: User specified data, specified when calling http_client_req()
The HTTP response from the server has many members so let’s focus on the most important ones.
http_status
: Textual description of the numeric HTTP status code, 3-digit integer code if valid HTTP response is given, 0 if null responserecv_buf
: Where the HTTP response is stored, provided by the userbody_frag_start
: Start address of the body contained in the HTTP response in recv_buf
body_frag_len
: Length of the body contained in recv_buf
In the following response callback, we print which HTTP request the HTTP response is a response to, through the user_data
field used when sending the HTTP request. Then the status of the HTTP response and if the reponse has a body, we want to print that as well.
static void response_cb(struct http_response *rsp,
enum http_final_call final_data,
void *user_data)
{
LOG_INF("Response to %s", (const char *)user_data);
LOG_INF("Response status: %s", rsp->http_status);
if (rsp->body_frag_len > 0) {
char body_buf[rsp->body_frag_len];
strncpy(body_buf, rsp->body_frag_start, rsp->body_frag_len);
LOG_INF("Received: %s", body_buf);
}
}
CAs opposed to the MQTT Library we covered in Lesson 4, the HTTP client library does not natively support implementing a TLS connection. We will do this by creating and configuring a TLS socket and passing it to the library.
This will be done using secure sockets and the TLS credentials subsystem in the Zephyr socket API. TLS credentials must be registered in the system before they can be used with secure sockets, using tls_credential_add()
.
After creating the socket, configure some TLS relevant configurations on the socket, using setsockopt()
, which has the following signature
We want to configure the socket with the security tag used to store the certificate and the hostname of the server we are connecting to. These are the options TLS_SEC_TAG_LIST
and TLS_HOSTNAME
, which reside at the protocol level SOL_TLS
.
At the time being, there is no dedicated HTTP server library. However, much of the HTTP client library APIs can be used to implement an HTTP server.
For an HTTP server, we do not need to create an HTTP request, however, we need to define the struct that holds HTTP requests. This struct will be passed on the function that responds to HTTP requests, by changing the state of an LED for example.
The HTTP request struct can be define as shown in the code snippet below.
struct http_req {
struct http_parser parser;
int socket;
bool received_all;
enum http_method method;
const char *url;
size_t url_len;
const char *body;
size_t body_len;
};
CTo parse incoming HTTP requests, we will need to use the HTTP parser library and link it to different functions corresponding to each part of the HTTP request being parsed. The parser function implementation would look like this:
static void parser_init(void)
{
http_parser_settings_init(&parser_settings);
parser_settings.on_body = on_body;
parser_settings.on_headers_complete = on_headers_complete;
parser_settings.on_message_begin = on_message_begin;
parser_settings.on_message_complete = on_message_complete;
parser_settings.on_url = on_url;
}
CTo set up the actual server functionality, we will use the function shown on the code snippet below
*sock = socket(bind_addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (*sock < 0) {
LOG_ERR("Failed to create TCP socket: %d", errno);
return -errno;
}
int err = bind(*sock, bind_addr, bind_addrlen);
if (err < 0) {
LOG_ERR("Failed to bind TCP socket %d", errno);
return -errno;
}
err = listen(*sock, MAX_CLIENT_QUEUE);
if (err < 0) {
LOG_ERR("Failed to listen on TCP socket %d", errno);
return -errno;
}
CThis function handles three main processes; it creates a socket, binds the server to the socket’s address, and finally it starts listening for incoming messages at that socket.