Skip to content

Commit

Permalink
net: http_server: allow specifying a default resource
Browse files Browse the repository at this point in the history
The _detail parameter of HTTP_SERVICE_DEFINE is used to optionally
specify a default resource detail, which will be served if no other
resource matches the URL.

Signed-off-by: Matt Rodgers <[email protected]>
  • Loading branch information
mrodgers-witekio committed Jan 10, 2025
1 parent ddd795a commit 6a1c5ef
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 17 deletions.
8 changes: 4 additions & 4 deletions include/zephyr/net/http/service.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ struct http_service_desc {
* @param[inout] _port Pointer to port associated with the service.
* @param _concurrent Maximum number of concurrent clients.
* @param _backlog Maximum number queued connections.
* @param _detail Implementation-specific detail associated with the service.
* @param _detail Default http_resource_detail if no other resource is matched
*/
#define HTTP_SERVICE_DEFINE_EMPTY(_name, _host, _port, _concurrent, _backlog, _detail) \
__z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, NULL, NULL)
Expand All @@ -140,7 +140,7 @@ struct http_service_desc {
* @param[inout] _port Pointer to port associated with the service.
* @param _concurrent Maximum number of concurrent clients.
* @param _backlog Maximum number queued connections.
* @param _detail Implementation-specific detail associated with the service.
* @param _detail Default http_resource_detail if no other resource is matched
* @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.
*/
Expand Down Expand Up @@ -168,7 +168,7 @@ struct http_service_desc {
* @param[inout] _port Pointer to port associated with the service.
* @param _concurrent Maximum number of concurrent clients.
* @param _backlog Maximum number queued connections.
* @param _detail Implementation-specific detail associated with the service.
* @param _detail Default http_resource_detail if no other resource is matched
*/
#define HTTP_SERVICE_DEFINE(_name, _host, _port, _concurrent, _backlog, _detail) \
extern struct http_resource_desc _CONCAT(_http_resource_desc_##_name, _list_start)[]; \
Expand All @@ -194,7 +194,7 @@ struct http_service_desc {
* @param[inout] _port Pointer to port associated with the service.
* @param _concurrent Maximum number of concurrent clients.
* @param _backlog Maximum number queued connections.
* @param _detail Implementation-specific detail associated with the service.
* @param _detail Default http_resource_detail if no other resource is matched
* @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.
*/
Expand Down
16 changes: 16 additions & 0 deletions subsys/net/lib/http/http_server_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,17 @@ static int compare_strings(const char *s1, const char *s2)
return 1; /* Strings are not equal */
}

static int path_len_without_query(const char *path)
{
int len = 0;

while ((path[len] != '\0') && (path[len] != '?')) {
len++;
}

return len;
}

static bool skip_this(struct http_resource_desc *resource, bool is_websocket)
{
struct http_resource_detail *detail;
Expand Down Expand Up @@ -761,6 +772,11 @@ struct http_resource_detail *get_resource_detail(const struct http_service_desc
}
}

if (service->detail != NULL) {
*path_len = path_len_without_query(path);
return service->detail;
}

NET_DBG("No match for %s", path);

return NULL;
Expand Down
1 change: 1 addition & 0 deletions tests/net/lib/http_server/common/sections-rom.ld
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
ITERABLE_SECTION_ROM(http_resource_desc_service_A, Z_LINK_ITERABLE_SUBALIGN)
ITERABLE_SECTION_ROM(http_resource_desc_service_B, Z_LINK_ITERABLE_SUBALIGN)
ITERABLE_SECTION_ROM(http_resource_desc_service_D, Z_LINK_ITERABLE_SUBALIGN)
ITERABLE_SECTION_ROM(http_resource_desc_service_E, Z_LINK_ITERABLE_SUBALIGN)
61 changes: 48 additions & 13 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));
HTTP_SERVICE_DEFINE(service_A, "a.service.com", &service_A_port, 4, 2, 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));
HTTP_SERVICE_DEFINE(service_B, "b.service.com", &service_B_port, 7, 3, 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,37 +67,42 @@ 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));
HTTP_SERVICE_DEFINE_EMPTY(service_C, "192.168.1.1", &service_C_port, 5, 9, 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));
HTTP_SERVICE_DEFINE(service_D, "2001:db8::1", &service_D_port, 7, 3, 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));
HTTP_RESOURCE_DEFINE(resource_8, service_D, "/fb?3.htm", RES(0));
HTTP_RESOURCE_DEFINE(resource_9, service_D, "/f*4.html", 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, 0, 0, DETAIL(0));
HTTP_RESOURCE_DEFINE(resource_10, service_E, "/index.html", RES(4));

ZTEST(http_service, test_HTTP_SERVICE_DEFINE)
{
zassert_ok(strcmp(service_A.host, "a.service.com"));
zassert_equal(service_A.port, &service_A_port);
zassert_equal(*service_A.port, 4242);
zassert_equal(service_A.detail, DETAIL(0));
zassert_equal(service_A.detail, NULL);
zassert_equal(service_A.concurrent, 4);
zassert_equal(service_A.backlog, 2);

zassert_ok(strcmp(service_B.host, "b.service.com"));
zassert_equal(service_B.port, &service_B_port);
zassert_equal(*service_B.port, 0);
zassert_equal(service_B.detail, DETAIL(1));
zassert_equal(service_B.detail, NULL);
zassert_equal(service_B.concurrent, 7);
zassert_equal(service_B.backlog, 3);

zassert_ok(strcmp(service_C.host, "192.168.1.1"));
zassert_equal(service_C.port, &service_C_port);
zassert_equal(*service_C.port, 5959);
zassert_equal(service_C.detail, DETAIL(2));
zassert_equal(service_C.detail, NULL);
zassert_equal(service_C.concurrent, 5);
zassert_equal(service_C.backlog, 9);
zassert_equal(service_C.res_begin, NULL);
Expand All @@ -110,7 +115,7 @@ ZTEST(http_service, test_HTTP_SERVICE_COUNT)

n_svc = 4273;
HTTP_SERVICE_COUNT(&n_svc);
zassert_equal(n_svc, 4);
zassert_equal(n_svc, 5);
}

ZTEST(http_service, test_HTTP_SERVICE_RESOURCE_COUNT)
Expand All @@ -127,6 +132,7 @@ ZTEST(http_service, test_HTTP_SERVICE_FOREACH)
size_t have_service_B = 0;
size_t have_service_C = 0;
size_t have_service_D = 0;
size_t have_service_E = 0;

HTTP_SERVICE_FOREACH(svc) {
if (svc == &service_A) {
Expand All @@ -137,17 +143,21 @@ ZTEST(http_service, test_HTTP_SERVICE_FOREACH)
have_service_C = 1;
} else if (svc == &service_D) {
have_service_D = 1;
} else if (svc == &service_E) {
have_service_E = 1;
} else {
zassert_unreachable("svc (%p) not equal to &service_A (%p), &service_B "
"(%p), or &service_C (%p)",
svc, &service_A, &service_B, &service_C);
zassert_unreachable("svc (%p) not equal to any defined service", svc);
}

n_svc++;
}

zassert_equal(n_svc, 4);
zassert_equal(have_service_A + have_service_B + have_service_C + have_service_D, n_svc);
zassert_equal(n_svc, 5);
zassert_equal(have_service_A, 1);
zassert_equal(have_service_B, 1);
zassert_equal(have_service_C, 1);
zassert_equal(have_service_D, 1);
zassert_equal(have_service_E, 1);
}

ZTEST(http_service, test_HTTP_RESOURCE_FOREACH)
Expand Down Expand Up @@ -361,6 +371,31 @@ ZTEST(http_service, test_HTTP_RESOURCE_WILDCARD)
zassert_equal(len, 0, "Length set");
}

ZTEST(http_service, test_HTTP_RESOURCE_DEFAULT)
{
#define NON_EXISTING_PATH "/this_path_is_not_registered"
struct http_resource_detail *res;
int len;

/* For a path that does exist, the correct resource should be returned */
res = CHECK_PATH(service_E, "/index.html", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(4), "Resource mismatch");

/* For a path that does not exist, the default resource should be returned */
res = CHECK_PATH(service_E, NON_EXISTING_PATH, &len);
zassert_not_null(res, "Cannot find resource");
zassert_equal(len, strlen(NON_EXISTING_PATH), "Length incorrect");
zassert_equal(res, RES(0), "Resource mismatch");

/* If query params are present, length should not include them */
res = CHECK_PATH(service_E, NON_EXISTING_PATH "?param=value", &len);
zassert_not_null(res, "Cannot find resource");
zassert_equal(len, strlen(NON_EXISTING_PATH), "Length incorrect");
zassert_equal(res, RES(0), "Resource mismatch");
}

extern void http_server_get_content_type_from_extension(char *url, char *content_type,
size_t content_type_size);

Expand Down

0 comments on commit 6a1c5ef

Please sign in to comment.