Skip to content

net: http: service: enhance HTTP service with optional custom socket create #92732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions doc/connectivity/networking/api/http_server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ macro:

static uint16_t http_service_port = 80;

HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10, NULL, NULL);
HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10, NULL, NULL, NULL);

Alternatively, an HTTPS service can be defined with
:c:macro:`HTTPS_SERVICE_DEFINE`:
Expand All @@ -124,7 +124,62 @@ Alternatively, an HTTPS service can be defined with
};

HTTPS_SERVICE_DEFINE(my_service, "0.0.0.0", &https_service_port, 1, 10,
NULL, NULL, sec_tag_list, sizeof(sec_tag_list));
NULL, NULL, NULL, sec_tag_list, sizeof(sec_tag_list));

Per-service configuration
=========================

HTTP services support individual service configuration,
for now it includes only socket creation through
the ``http_service_config`` structure. This allows applications to customize
socket creation behavior, for example to set specific socket options or use
custom socket types.

To use custom socket creation:

.. code-block:: c

static int my_socket_create(const struct http_service_desc *svc, int af, int proto)
{
int fd;

/* Create socket with custom parameters */
fd = zsock_socket(af, SOCK_STREAM, proto);
if (fd < 0) {
return fd;
}

/* Set custom socket options */
/* Add any other custom socket configuration */

return fd;
}

static const struct http_service_config my_service_config = {
.socket_create = my_socket_create,
};

static uint16_t http_service_port = 80;

HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10,
NULL, NULL, &my_service_config);

The custom socket creation function receives:
- ``svc``: Pointer to the service descriptor
- ``af``: Address family (AF_INET or AF_INET6)
- ``proto``: Protocol (IPPROTO_TCP or IPPROTO_TLS_1_2 for HTTPS)

The function should return the socket file descriptor on success, or a negative error code on failure.

If no custom configuration is needed, simply pass ``NULL`` for the config parameter:

.. code-block:: c

HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10,
NULL, NULL, NULL);

Fallback Resources
==================

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
Expand Down Expand Up @@ -160,7 +215,7 @@ customised 404 response.
};

/* 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);
HTTP_SERVICE_DEFINE(my_service, "0.0.0.0", &http_service_port, 1, 10, NULL, &default_detail, NULL);

.. note::

Expand Down
5 changes: 5 additions & 0 deletions doc/releases/migration-guide-4.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ Bluetooth
Networking
**********

* The HTTP server now respects the configured ``_config`` value. Check that
you provide applicable value to :c:macro:`HTTP_SERVICE_DEFINE_EMPTY`,
:c:macro:`HTTPS_SERVICE_DEFINE_EMPTY`, :c:macro:`HTTP_SERVICE_DEFINE` and
:c:macro:`HTTPS_SERVICE_DEFINE`.

Other subsystems
****************

Expand Down
37 changes: 28 additions & 9 deletions include/zephyr/net/http/service.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ struct http_service_runtime_data {
int num_clients;
};

struct http_service_desc;

/** Custom socket creation function type */
typedef int (*http_socket_create_fn)(const struct http_service_desc *svc, int af, int proto);

/** HTTP service configuration */
struct http_service_config {
/** Custom socket creation for the service if needed */
http_socket_create_fn socket_create;
/* If any more service-specific configuration is needed, it can be added here. */
};

struct http_service_desc {
const char *host;
uint16_t *port;
Expand All @@ -80,14 +92,15 @@ struct http_service_desc {
struct http_resource_desc *res_begin;
struct http_resource_desc *res_end;
struct http_resource_detail *res_fallback;
const struct http_service_config *config;
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
const sec_tag_t *sec_tag_list;
size_t sec_tag_list_size;
#endif
};

#define __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, _res_begin, _res_end, ...) \
_res_fallback, _res_begin, _res_end, _config, ...) \
BUILD_ASSERT(_concurrent <= CONFIG_HTTP_SERVER_MAX_CLIENTS, \
"can't accept more then MAX_CLIENTS"); \
BUILD_ASSERT(_backlog > 0, "backlog can't be 0"); \
Expand All @@ -104,6 +117,7 @@ struct http_service_desc {
.res_begin = (_res_begin), \
.res_end = (_res_end), \
.res_fallback = (_res_fallback), \
.config = (_config), \
COND_CODE_1(CONFIG_NET_SOCKETS_SOCKOPT_TLS, \
(.sec_tag_list = COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (NULL), \
(GET_ARG_N(1, __VA_ARGS__))),), ()) \
Expand Down Expand Up @@ -133,11 +147,12 @@ struct http_service_desc {
* @param _backlog Maximum number of queued connections. (min. 1)
* @param _detail User-defined detail associated with the service.
* @param _res_fallback Fallback resource to be served if no other resource matches path
* @param _config Pointer to http_service_config structure (can be NULL for default behavior)
*/
#define HTTP_SERVICE_DEFINE_EMPTY(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback) \
_res_fallback, _config) \
__z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, NULL, NULL)
_res_fallback, NULL, NULL, _config)

/**
* @brief Define an HTTPS service without static resources.
Expand All @@ -158,13 +173,14 @@ struct http_service_desc {
* @param _backlog Maximum number of queued connections. (min. 1)
* @param _detail User-defined detail associated with the service.
* @param _res_fallback Fallback resource to be served if no other resource matches path
* @param _config Pointer to http_service_config structure (can be NULL for default behavior)
* @param _sec_tag_list TLS security tag list used to setup a HTTPS socket.
* @param _sec_tag_list_size TLS security tag list size used to setup a HTTPS socket.
*/
#define HTTPS_SERVICE_DEFINE_EMPTY(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, _sec_tag_list, _sec_tag_list_size) \
_res_fallback, _config, _sec_tag_list, _sec_tag_list_size) \
__z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, NULL, NULL, \
_res_fallback, NULL, NULL, _config, \
_sec_tag_list, _sec_tag_list_size); \
BUILD_ASSERT(IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS), \
"TLS is required for HTTP secure (CONFIG_NET_SOCKETS_SOCKOPT_TLS)")
Expand All @@ -188,14 +204,16 @@ struct http_service_desc {
* @param _backlog Maximum number of queued connections. (min. 1)
* @param _detail User-defined detail associated with the service.
* @param _res_fallback Fallback resource to be served if no other resource matches path
* @param _config Pointer to http_service_config structure (can be NULL for default behavior)
*/
#define HTTP_SERVICE_DEFINE(_name, _host, _port, _concurrent, _backlog, _detail, _res_fallback) \
#define HTTP_SERVICE_DEFINE(_name, _host, _port, _concurrent, _backlog, _detail, _res_fallback, \
_config) \
extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_start)[]; \
extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_end)[]; \
__z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, \
&_CONCAT(_http_resource_desc_##_name, _list_start)[0], \
&_CONCAT(_http_resource_desc_##_name, _list_end)[0]);
&_CONCAT(_http_resource_desc_##_name, _list_end)[0], _config);

/**
* @brief Define an HTTPS service with static resources.
Expand All @@ -216,17 +234,18 @@ struct http_service_desc {
* @param _backlog Maximum number of queued connections. (min. 1)
* @param _detail User-defined detail associated with the service.
* @param _res_fallback Fallback resource to be served if no other resource matches path
* @param _config Pointer to http_service_config structure (can be NULL for default behavior)
* @param _sec_tag_list TLS security tag list used to setup a HTTPS socket.
* @param _sec_tag_list_size TLS security tag list size used to setup a HTTPS socket.
*/
#define HTTPS_SERVICE_DEFINE(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, _sec_tag_list, _sec_tag_list_size) \
_res_fallback, _config, _sec_tag_list, _sec_tag_list_size) \
extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_start)[]; \
extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_end)[]; \
__z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, \
_res_fallback, \
&_CONCAT(_http_resource_desc_##_name, _list_start)[0], \
&_CONCAT(_http_resource_desc_##_name, _list_end)[0], \
&_CONCAT(_http_resource_desc_##_name, _list_end)[0], _config, \
_sec_tag_list, _sec_tag_list_size); \
BUILD_ASSERT(IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS), \
"TLS is required for HTTP secure (CONFIG_NET_SOCKETS_SOCKOPT_TLS)")
Expand Down
3 changes: 2 additions & 1 deletion include/zephyr/shell/shell_websocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ int shell_websocket_enable(const struct shell *sh);
SHELL_WEBSOCKET_SERVICE_COUNT, \
NULL, \
NULL, \
NULL, \
_sec_tag_list, \
_sec_tag_list_size); \
DEFINE_WEBSOCKET_SERVICE(_service); \
Expand All @@ -137,7 +138,7 @@ int shell_websocket_enable(const struct shell *sh);
&SHELL_WS_PORT_NAME(_service), \
SHELL_WEBSOCKET_SERVICE_COUNT, \
SHELL_WEBSOCKET_SERVICE_COUNT, \
NULL, NULL); \
NULL, NULL, NULL); \
DEFINE_WEBSOCKET_SERVICE(_service)

#endif /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */
Expand Down
4 changes: 2 additions & 2 deletions samples/net/prometheus/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct app_context {
#if defined(CONFIG_NET_SAMPLE_HTTP_SERVICE)
static uint16_t test_http_service_port = CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT;
HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_http_service_port,
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL);
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL, NULL);

static int dyn_handler(struct http_client_ctx *client, enum http_data_status status,
const struct http_request_ctx *request_ctx,
Expand Down Expand Up @@ -99,7 +99,7 @@ const sec_tag_t sec_tag_list_verify_none[] = {

static uint16_t test_https_service_port = CONFIG_NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT;
HTTPS_SERVICE_DEFINE(test_https_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_https_service_port,
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL, sec_tag_list_verify_none,
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL, NULL, sec_tag_list_verify_none,
sizeof(sec_tag_list_verify_none));

HTTP_RESOURCE_DEFINE(index_html_gz_resource_https, test_https_service, "/metrics",
Expand Down
4 changes: 2 additions & 2 deletions samples/net/sockets/http_server/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ struct http_resource_detail_websocket ws_netstats_resource_detail = {
#if defined(CONFIG_NET_SAMPLE_HTTP_SERVICE)
static uint16_t test_http_service_port = CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT;
HTTP_SERVICE_DEFINE(test_http_service, NULL, &test_http_service_port,
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL);
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL, NULL);

HTTP_RESOURCE_DEFINE(index_html_gz_resource, test_http_service, "/",
&index_html_gz_resource_detail);
Expand Down Expand Up @@ -281,7 +281,7 @@ static const sec_tag_t sec_tag_list_verify_none[] = {

static uint16_t test_https_service_port = CONFIG_NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT;
HTTPS_SERVICE_DEFINE(test_https_service, NULL, &test_https_service_port,
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL, sec_tag_list_verify_none,
CONFIG_HTTP_SERVER_MAX_CLIENTS, 10, NULL, NULL, NULL, sec_tag_list_verify_none,
sizeof(sec_tag_list_verify_none));

HTTP_RESOURCE_DEFINE(index_html_gz_resource_https, test_https_service, "/",
Expand Down
6 changes: 5 additions & 1 deletion subsys/net/lib/http/http_server_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ int http_server_init(struct http_server_ctx *ctx)
proto = IPPROTO_TCP;
}

fd = zsock_socket(af, SOCK_STREAM, proto);
if (svc->config != NULL && svc->config->socket_create != NULL) {
fd = svc->config->socket_create(svc, af, proto);
} else {
fd = zsock_socket(af, SOCK_STREAM, proto);
}
if (fd < 0) {
LOG_ERR("socket: %d", errno);
failed++;
Expand Down
2 changes: 1 addition & 1 deletion tests/net/lib/http_client/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

static uint16_t test_http_service_port = SERVER_PORT;
HTTP_SERVICE_DEFINE(test_http_service, SERVER_IPV6_ADDR,
&test_http_service_port, 1, 10, NULL, NULL);
&test_http_service_port, 1, 10, NULL, NULL, NULL);

static const char static_resource_payload[] = LOREM_IPSUM_SHORT;
struct http_resource_detail_static static_resource_detail = {
Expand Down
10 changes: 5 additions & 5 deletions tests/net/lib/http_server/common/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ static struct http_resource_detail detail[] = {
* the paths (and implementation-specific details) are known at compile time.
*/
static const uint16_t service_A_port = 4242;
HTTP_SERVICE_DEFINE(service_A, "a.service.com", &service_A_port, 4, 2, DETAIL(0), NULL);
HTTP_SERVICE_DEFINE(service_A, "a.service.com", &service_A_port, 4, 2, DETAIL(0), NULL, NULL);
HTTP_RESOURCE_DEFINE(resource_0, service_A, "/", RES(0));
HTTP_RESOURCE_DEFINE(resource_1, service_A, "/index.html", RES(1));
HTTP_RESOURCE_DEFINE(resource_2, service_A, "/fs/*", RES(5));

/* ephemeral port of 0 */
static uint16_t service_B_port;
HTTP_SERVICE_DEFINE(service_B, "b.service.com", &service_B_port, 7, 3, DETAIL(1), NULL);
HTTP_SERVICE_DEFINE(service_B, "b.service.com", &service_B_port, 7, 3, DETAIL(1), NULL, NULL);
HTTP_RESOURCE_DEFINE(resource_3, service_B, "/foo.htm", RES(2));
HTTP_RESOURCE_DEFINE(resource_4, service_B, "/bar/baz.php", RES(3));

Expand All @@ -67,11 +67,11 @@ HTTP_RESOURCE_DEFINE(resource_4, service_B, "/bar/baz.php", RES(3));
* runtime.
*/
static const uint16_t service_C_port = 5959;
HTTP_SERVICE_DEFINE_EMPTY(service_C, "192.168.1.1", &service_C_port, 5, 9, DETAIL(2), NULL);
HTTP_SERVICE_DEFINE_EMPTY(service_C, "192.168.1.1", &service_C_port, 5, 9, DETAIL(2), NULL, NULL);

/* Wildcard resources */
static uint16_t service_D_port = service_A_port + 1;
HTTP_SERVICE_DEFINE(service_D, "2001:db8::1", &service_D_port, 7, 3, DETAIL(3), NULL);
HTTP_SERVICE_DEFINE(service_D, "2001:db8::1", &service_D_port, 7, 3, DETAIL(3), NULL, NULL);
HTTP_RESOURCE_DEFINE(resource_5, service_D, "/foo1.htm*", RES(0));
HTTP_RESOURCE_DEFINE(resource_6, service_D, "/fo*", RES(1));
HTTP_RESOURCE_DEFINE(resource_7, service_D, "/f[ob]o3.html", RES(1));
Expand All @@ -82,7 +82,7 @@ HTTP_RESOURCE_DEFINE(resource_12, service_D, "/foo/b?r", RES(3));

/* Default resource in case of no match */
static uint16_t service_E_port = 8080;
HTTP_SERVICE_DEFINE(service_E, "192.0.2.1", &service_E_port, 1, 1, NULL, DETAIL(0));
HTTP_SERVICE_DEFINE(service_E, "192.0.2.1", &service_E_port, 1, 1, NULL, DETAIL(0), NULL);
HTTP_RESOURCE_DEFINE(resource_10, service_E, "/index.html", RES(4));

ZTEST(http_service, test_HTTP_SERVICE_DEFINE)
Expand Down
2 changes: 1 addition & 1 deletion tests/net/lib/http_server/core/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE,

static uint16_t test_http_service_port = SERVER_PORT;
HTTP_SERVICE_DEFINE(test_http_service, SERVER_IPV4_ADDR,
&test_http_service_port, 1, 10, NULL, NULL);
&test_http_service_port, 1, 10, NULL, NULL, NULL);

static const char static_resource_payload[] = TEST_STATIC_PAYLOAD;
struct http_resource_detail_static static_resource_detail = {
Expand Down
2 changes: 1 addition & 1 deletion tests/net/lib/http_server/crime/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ static const unsigned char compressed_inc_file[] = {
static uint16_t test_http_service_port = SERVER_PORT;
HTTP_SERVICE_DEFINE(test_http_service, MY_IPV4_ADDR,
&test_http_service_port, 1,
10, NULL, NULL);
10, NULL, NULL, NULL);

struct http_resource_detail_static index_html_gz_resource_detail = {
.common = {
Expand Down
2 changes: 1 addition & 1 deletion tests/net/lib/http_server/tls/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ static const sec_tag_t server_tag_list_verify[] = {

static uint16_t test_http_service_port = SERVER_PORT;
HTTPS_SERVICE_DEFINE(test_http_service, MY_IPV4_ADDR, &test_http_service_port,
1, 10, NULL, NULL, server_tag_list_verify,
1, 10, NULL, NULL, NULL, server_tag_list_verify,
sizeof(server_tag_list_verify));

static const unsigned char ca[] = {
Expand Down