You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
16 KiB
451 lines
16 KiB
.. _http_server_interface: |
|
|
|
HTTP Server |
|
########### |
|
|
|
.. contents:: |
|
:local: |
|
:depth: 2 |
|
|
|
Overview |
|
******** |
|
|
|
Zephyr provides an HTTP server library, which allows to register HTTP services |
|
and HTTP resources associated with those services. The server creates a listening |
|
socket for every registered service, and handles incoming client connections. |
|
It's possible to communicate over a plain TCP socket (HTTP) or a TLS socket (HTTPS). |
|
Both, HTTP/1.1 (RFC 2616) and HTTP/2 (RFC 9113) protocol versions are supported. |
|
|
|
The server operation is generally transparent for the application, running in a |
|
background thread. The application can control the server activity with |
|
respective API functions. |
|
|
|
Certain resource types (for example dynamic resource) provide resource-specific |
|
application callbacks, allowing the server to interact with the application (for |
|
instance provide resource content, or process request payload). |
|
|
|
Currently, the following resource types are supported: |
|
|
|
* Static resources - content defined compile-time, cannot be modified at runtime |
|
(:c:enumerator:`HTTP_RESOURCE_TYPE_STATIC`). |
|
|
|
* Static file system resources - the path at which the filesystem is mounted, |
|
and the URL at which the filesystem is made available are fixed at build time, |
|
but the content within the filesystem can be changed dynamically. This means that |
|
the files can be created, modified or deleted by some other code outside the HTTP |
|
server (:c:enumerator:`HTTP_RESOURCE_TYPE_STATIC_FS`). |
|
|
|
* Dynamic resources - content provided at runtime by respective application |
|
callback (:c:enumerator:`HTTP_RESOURCE_TYPE_DYNAMIC`). |
|
|
|
* Websocket resources - allowing to establish Websocket connections with the |
|
server (:c:enumerator:`HTTP_RESOURCE_TYPE_WEBSOCKET`). |
|
|
|
Zephyr provides a sample demonstrating HTTP(s) server operation and various |
|
resource types usage. See :zephyr:code-sample:`sockets-http-server` for more |
|
information. |
|
|
|
Server Setup |
|
************ |
|
|
|
A few prerequisites are needed in order to enable HTTP server functionality in |
|
the application. |
|
|
|
First of all, the HTTP server has to be enabled in applications configuration file |
|
with :kconfig:option:`CONFIG_HTTP_SERVER` Kconfig option: |
|
|
|
.. code-block:: cfg |
|
:caption: ``prj.conf`` |
|
|
|
CONFIG_HTTP_SERVER=y |
|
|
|
All HTTP services and HTTP resources are placed in a dedicated linker section. |
|
The linker section for services is predefined locally, however the application |
|
is responsible for defining linker sections for resources associated with |
|
respective services. Linker section names for resources should be prefixed with |
|
``http_resource_desc_``, appended with the service name. |
|
|
|
Linker sections for resources should be defined in a linker file. For example, |
|
for a service named ``my_service``, the linker section shall be defined as follows: |
|
|
|
.. code-block:: c |
|
:caption: ``sections-rom.ld`` |
|
|
|
#include <zephyr/linker/iterable_sections.h> |
|
|
|
ITERABLE_SECTION_ROM(http_resource_desc_my_service, Z_LINK_ITERABLE_SUBALIGN) |
|
|
|
Finally, the linker file and linker section have to be added to your application |
|
using CMake: |
|
|
|
.. code-block:: cmake |
|
:caption: ``CMakeLists.txt`` |
|
|
|
zephyr_linker_sources(SECTIONS sections-rom.ld) |
|
zephyr_linker_section(NAME http_resource_desc_my_service |
|
KVMA RAM_REGION GROUP RODATA_REGION) |
|
|
|
.. note:: |
|
|
|
You need to define a separate linker section for each HTTP service |
|
registered in the system. |
|
|
|
Sample Usage |
|
************ |
|
|
|
Services |
|
======== |
|
|
|
The application needs to define an HTTP service (or multiple services), with |
|
the same name as used for the linker section with :c:macro:`HTTP_SERVICE_DEFINE` |
|
macro: |
|
|
|
.. code-block:: c |
|
|
|
#include <zephyr/net/http/service.h> |
|
|
|
static uint16_t http_service_port = 80; |
|
|
|
HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10, NULL, NULL); |
|
|
|
Alternatively, an HTTPS service can be defined with |
|
:c:macro:`HTTPS_SERVICE_DEFINE`: |
|
|
|
.. code-block:: c |
|
|
|
#include <zephyr/net/http/service.h> |
|
#include <zephyr/net/tls_credentials.h> |
|
|
|
#define HTTP_SERVER_CERTIFICATE_TAG 1 |
|
|
|
static uint16_t https_service_port = 443; |
|
static const sec_tag_t sec_tag_list[] = { |
|
HTTP_SERVER_CERTIFICATE_TAG, |
|
}; |
|
|
|
HTTPS_SERVICE_DEFINE(my_service, "0.0.0.0", &https_service_port, 1, 10, |
|
NULL, NULL, sec_tag_list, sizeof(sec_tag_list)); |
|
|
|
The ``_res_fallback`` parameter can be used when defining an HTTP/HTTPS service to |
|
specify a fallback resource which will be used if no other resource matches the |
|
URL. This can be used for example to serve an index page for all unknown paths |
|
(useful for a single-page app which handles routing in the frontend), or for a |
|
customised 404 response. |
|
|
|
.. code-block:: c |
|
|
|
static int default_handler(struct http_client_ctx *client, enum http_data_status status, |
|
const struct http_request_ctx *request_ctx, |
|
struct http_response_ctx *response_ctx, void *user_data) |
|
{ |
|
static const char response_404[] = "Oops, page not found!"; |
|
|
|
if (status == HTTP_SERVER_DATA_FINAL) { |
|
response_ctx->status = 404; |
|
response_ctx->body = response_404; |
|
response_ctx->body_len = sizeof(response_404) - 1; |
|
response_ctx->final_chunk = true; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static struct http_resource_detail_dynamic default_detail = { |
|
.common = { |
|
.type = HTTP_RESOURCE_TYPE_DYNAMIC, |
|
.bitmask_of_supported_http_methods = BIT(HTTP_GET), |
|
}, |
|
.cb = default_handler, |
|
.user_data = NULL, |
|
}; |
|
|
|
/* Register a fallback resource to handle any unknown path */ |
|
HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10, NULL, &default_detail); |
|
|
|
.. note:: |
|
|
|
HTTPS services rely on TLS credentials being registered in the system. |
|
See :ref:`sockets_tls_credentials_subsys` for information on how to |
|
configure TLS credentials in the system. |
|
|
|
Once HTTP(s) service is defined, resources can be registered for it with |
|
:c:macro:`HTTP_RESOURCE_DEFINE` macro. |
|
|
|
Application can enable resource wildcard support by enabling |
|
:kconfig:option:`CONFIG_HTTP_SERVER_RESOURCE_WILDCARD` option. When this |
|
option is set, then it is possible to match several incoming HTTP requests |
|
with just one resource handler. The `fnmatch() |
|
<https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html>`__ |
|
POSIX API function is used to match the pattern in the URL paths. |
|
|
|
Example: |
|
|
|
.. code-block:: c |
|
|
|
HTTP_RESOURCE_DEFINE(my_resource, my_service, "/foo*", &resource_detail); |
|
|
|
This would match all URLs that start with a string ``foo``. See |
|
`POSIX.2 chapter 2.13 |
|
<https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13>`__ |
|
for pattern matching syntax description. |
|
|
|
Static resources |
|
================ |
|
|
|
Static resource content is defined build-time and is immutable. The following |
|
example shows how gzip compressed webpage can be defined as a static resource |
|
in the application: |
|
|
|
.. code-block:: c |
|
|
|
static const uint8_t index_html_gz[] = { |
|
#include "index.html.gz.inc" |
|
}; |
|
|
|
struct http_resource_detail_static index_html_gz_resource_detail = { |
|
.common = { |
|
.type = HTTP_RESOURCE_TYPE_STATIC, |
|
.bitmask_of_supported_http_methods = BIT(HTTP_GET), |
|
.content_encoding = "gzip", |
|
}, |
|
.static_data = index_html_gz, |
|
.static_data_len = sizeof(index_html_gz), |
|
}; |
|
|
|
HTTP_RESOURCE_DEFINE(index_html_gz_resource, my_service, "/", |
|
&index_html_gz_resource_detail); |
|
|
|
The resource content and content encoding is application specific. For the above |
|
example, a gzip compressed webpage can be generated during build, by adding the |
|
following code to the application's ``CMakeLists.txt`` file: |
|
|
|
.. code-block:: cmake |
|
:caption: ``CMakeLists.txt`` |
|
|
|
set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) |
|
set(source_file_index src/index.html) |
|
generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.gz.inc --gzip) |
|
|
|
where ``src/index.html`` is the location of the webpage to be compressed. |
|
|
|
Static filesystem resources |
|
=========================== |
|
|
|
Static filesystem resource content is defined build-time and is immutable. Note that only |
|
``GET`` operation is supported, user is not able to upload files to the filesystem. The following |
|
example shows how the path can be defined as a static resource in the application: |
|
|
|
.. code-block:: c |
|
|
|
struct http_resource_detail_static_fs static_fs_resource_detail = { |
|
.common = { |
|
.type = HTTP_RESOURCE_TYPE_STATIC_FS, |
|
.bitmask_of_supported_http_methods = BIT(HTTP_GET), |
|
}, |
|
.fs_path = "/lfs1/www", |
|
}; |
|
|
|
HTTP_RESOURCE_DEFINE(static_fs_resource, my_service, "*", &static_fs_resource_detail); |
|
|
|
All files located in /lfs1/www are made available to the client. If a file is |
|
gzipped, .gz must be appended to the file name (e.g. index.html.gz), then the |
|
server delivers index.html.gz when the client requests index.html and adds gzip |
|
content-encoding to the HTTP header. |
|
|
|
The content type is evaluated based on the file extension. The server supports |
|
.html, .js, .css, .jpg, .png and .svg. More content types can be provided with the |
|
:c:macro:`HTTP_SERVER_CONTENT_TYPE` macro. All other files are provided with the |
|
content type text/html. |
|
|
|
.. code-block:: c |
|
|
|
HTTP_SERVER_CONTENT_TYPE(json, "application/json") |
|
|
|
Dynamic resources |
|
================= |
|
|
|
For dynamic resource, a resource callback is registered to exchange data between |
|
the server and the application. |
|
|
|
The following example code shows how to register a dynamic resource with a simple |
|
resource handler, which echoes received data back to the client: |
|
|
|
.. code-block:: c |
|
|
|
static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, |
|
const struct http_request_ctx *request_ctx, |
|
struct http_response_ctx *response_ctx, void *user_data) |
|
{ |
|
#define MAX_TEMP_PRINT_LEN 32 |
|
static char print_str[MAX_TEMP_PRINT_LEN]; |
|
enum http_method method = client->method; |
|
static size_t processed; |
|
|
|
__ASSERT_NO_MSG(buffer != NULL); |
|
|
|
if (status == HTTP_SERVER_DATA_ABORTED) { |
|
LOG_DBG("Transaction aborted after %zd bytes.", processed); |
|
processed = 0; |
|
return 0; |
|
} |
|
|
|
processed += request_ctx->data_len; |
|
|
|
snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", |
|
http_method_str(method), request_ctx->data_len); |
|
LOG_HEXDUMP_DBG(request_ctx->data, request_ctx->data_len, print_str); |
|
|
|
if (status == HTTP_SERVER_DATA_FINAL) { |
|
LOG_DBG("All data received (%zd bytes).", processed); |
|
processed = 0; |
|
} |
|
|
|
/* Echo data back to client */ |
|
response_ctx->body = request_ctx->data; |
|
response_ctx->body_len = request_ctx->data_len; |
|
response_ctx->final_chunk = (status == HTTP_SERVER_DATA_FINAL); |
|
|
|
return 0; |
|
} |
|
|
|
struct http_resource_detail_dynamic dyn_resource_detail = { |
|
.common = { |
|
.type = HTTP_RESOURCE_TYPE_DYNAMIC, |
|
.bitmask_of_supported_http_methods = |
|
BIT(HTTP_GET) | BIT(HTTP_POST), |
|
}, |
|
.cb = dyn_handler, |
|
.user_data = NULL, |
|
}; |
|
|
|
HTTP_RESOURCE_DEFINE(dyn_resource, my_service, "/dynamic", |
|
&dyn_resource_detail); |
|
|
|
|
|
The resource callback may be called multiple times for a single request, hence |
|
the application should be able to keep track of the received data progress. |
|
|
|
The ``status`` field informs the application about the progress in passing |
|
request payload from the server to the application. As long as the status |
|
reports :c:enumerator:`HTTP_SERVER_DATA_MORE`, the application should expect |
|
more data to be provided in a consecutive callback calls. |
|
Once all request payload has been passed to the application, the server reports |
|
:c:enumerator:`HTTP_SERVER_DATA_FINAL` status. In case of communication errors |
|
during request processing (for example client closed the connection before |
|
complete payload has been received), the server reports |
|
:c:enumerator:`HTTP_SERVER_DATA_ABORTED`. Either of the two events indicate that |
|
the application shall reset any progress recorded for the resource, and await |
|
a new request to come. The server guarantees that the resource can only be |
|
accessed by single client at a time. |
|
|
|
The ``request_ctx`` parameter is used to pass request data to the application: |
|
|
|
* The ``data`` and ``data_len`` fields pass request data to the application. |
|
|
|
* The ``headers``, ``header_count`` and ``headers_status`` fields pass request |
|
headers to the application, if |
|
:kconfig:option:`CONFIG_HTTP_SERVER_CAPTURE_HEADERS` is enabled. |
|
|
|
The ``response_ctx`` field is used by the application to pass response data to |
|
the HTTP server: |
|
|
|
* The ``status`` field allows the application to send an HTTP response code. If |
|
not populated, the response code will be 200 by default. |
|
|
|
* The ``headers`` and ``header_count`` fields can be used for the application to |
|
send any arbitrary HTTP headers. If not populated, only Transfer-Encoding and |
|
Content-Type are sent by default. The callback may override the Content-Type |
|
if desired. |
|
|
|
* The ``body`` and ``body_len`` fields are used to send body data. |
|
|
|
* The ``final_chunk`` field is used to indicate that the application has no more |
|
response data to send. |
|
|
|
Headers and/or response codes may only be sent in the first populated |
|
``response_ctx``, after which only further body data is allowed in subsequent |
|
callbacks. |
|
|
|
The server will call the resource callback until it provided all request data |
|
to the application, and the application reports there is no more data to include |
|
in the reply. |
|
|
|
Websocket resources |
|
=================== |
|
|
|
Websocket resources register an application callback, which is called when a |
|
Websocket connection upgrade takes place. The callback is provided with a socket |
|
descriptor corresponding to the underlying TCP/TLS connection. Once called, |
|
the application takes full control over the socket, i. e. is responsible to |
|
release it when done. |
|
|
|
.. code-block:: c |
|
|
|
static int ws_socket; |
|
static uint8_t ws_recv_buffer[1024]; |
|
|
|
int ws_setup(int sock, struct http_request_ctx *request_ctx, void *user_data) |
|
{ |
|
ws_socket = sock; |
|
return 0; |
|
} |
|
|
|
struct http_resource_detail_websocket ws_resource_detail = { |
|
.common = { |
|
.type = HTTP_RESOURCE_TYPE_WEBSOCKET, |
|
/* We need HTTP/1.1 Get method for upgrading */ |
|
.bitmask_of_supported_http_methods = BIT(HTTP_GET), |
|
}, |
|
.cb = ws_setup, |
|
.data_buffer = ws_recv_buffer, |
|
.data_buffer_len = sizeof(ws_recv_buffer), |
|
.user_data = NULL, /* Fill this for any user specific data */ |
|
}; |
|
|
|
HTTP_RESOURCE_DEFINE(ws_resource, my_service, "/", &ws_resource_detail); |
|
|
|
The above minimalistic example shows how to register a Websocket resource with |
|
a simple callback, used only to store the socket descriptor provided. Further |
|
processing of the Websocket connection is application-specific, hence outside |
|
of scope of this guide. See :zephyr:code-sample:`sockets-http-server` for an |
|
example Websocket-based echo service implementation. |
|
|
|
Accessing request headers |
|
========================= |
|
|
|
The application can register an interest in any specific HTTP request headers. |
|
These headers are then stored for each incoming request, and can be accessed |
|
from within a dynamic resource callback. Request headers are only included in |
|
the first callback for a given request, and are not passed to any subsequent |
|
callbacks. |
|
|
|
This feature must first be enabled with |
|
:kconfig:option:`CONFIG_HTTP_SERVER_CAPTURE_HEADERS` Kconfig option. |
|
|
|
Then the application can register headers to be captured, and read the values |
|
from within the dynamic resource callback: |
|
|
|
.. code-block:: c |
|
|
|
HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_user_agent, "User-Agent"); |
|
|
|
static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, |
|
uint8_t *buffer, size_t len, void *user_data) |
|
{ |
|
size_t header_count = client->header_capture_ctx.count; |
|
const struct http_header *headers = client->header_capture_ctx.headers; |
|
|
|
LOG_INF("Captured %d headers with request", header_count); |
|
|
|
for (uint32_t i = 0; i < header_count; i++) { |
|
LOG_INF("Header: '%s: %s'", headers[i].name, headers[i].value); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
API Reference |
|
************* |
|
|
|
.. doxygengroup:: http_service |
|
.. doxygengroup:: http_server
|
|
|