Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

HTTP library

HTTP client library

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

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);
C
  • header_fields: the HTTP header fields. For example: Connection, Content-Type, Authorization
  • method: 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 request
  • protocol: HTTP version being used, necessary for the header. For example “HTTP/1.1”
  • payload: Payload of HTTP request
  • payload_len: Payload length, used to calculate HTTP Content-Length header
  • response: User supplied callback function to call when response is received
  • recv_buf: User supplied buffer where received data is stored
  • recv_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");
C
http_client_req() signature
  • sock: Socket file descriptor of the socket connected to the HTTP server
  • req: Pointer to the HTTP request
  • timeout: Maximum timeout to wait for data
  • user_data: User specified data that is passed to the callback, can be used for instance to identify which HTTP request it is responding to

Define a response callback function

When 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

http_response_cb_t() 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 response
  • recv_buf: Where the HTTP response is stored, provided by the user
  • body_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);
	} 
}
C

Using HTTP with TLS

As 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

HTTP server

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;
};
C

To 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;
}
C

To 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;
}
C

This 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.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.