Skip to content
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

net: http: Add compression support in HTTP server #86490

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
18 changes: 18 additions & 0 deletions include/zephyr/net/http/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ struct http_resource_detail_static_fs {
const char *fs_path;
};

/** @brief HTTP compressions */
enum http_compression {
HTTP_NONE = 0, /**< NONE */
HTTP_GZIP = 1, /**< GZIP */
HTTP_COMPRESS = 2, /**< COMPRESS */
HTTP_DEFLATE = 3, /**< DEFLATE */
HTTP_BR = 4, /**< BR */
HTTP_ZSTD = 5 /**< ZSTD */
};

/** @cond INTERNAL_HIDDEN */
/* Make sure that the common is the first in the struct. */
BUILD_ASSERT(offsetof(struct http_resource_detail_static_fs, common) == 0);
Expand Down Expand Up @@ -475,6 +485,11 @@ struct http_client_ctx {
IF_ENABLED(CONFIG_WEBSOCKET, (uint8_t ws_sec_key[HTTP_SERVER_WS_MAX_SEC_KEY_LEN]));
/** @endcond */

/** @cond INTERNAL_HIDDEN */
/** Client supported compression. */
IF_ENABLED(CONFIG_HTTP_SERVER_COMPRESSION, (uint8_t supported_compression));
/** @endcond */

/** Flag indicating that HTTP2 preface was sent. */
bool preface_sent : 1;

Expand All @@ -493,6 +508,9 @@ struct http_client_ctx {
/** Flag indicating Websocket key is being processed. */
bool websocket_sec_key_next : 1;

/** Flag indicating accept encoding is being processed. */
IF_ENABLED(CONFIG_HTTP_SERVER_COMPRESSION, (bool accept_encoding_next: 1));

/** The next frame on the stream is expectd to be a continuation frame. */
bool expect_continuation : 1;
};
Expand Down
1 change: 1 addition & 0 deletions subsys/net/lib/http/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_HTTP_SERVER http_server_core.c
http_server_http2.c
http_hpack.c
http_huffman.c)
zephyr_library_sources_ifdef(CONFIG_HTTP_SERVER_COMPRESSION http_compression.c)
if(CONFIG_HTTP_SERVER AND CONFIG_WEBSOCKET)
zephyr_library_sources(http_server_ws.c)
zephyr_library_link_libraries_ifdef(CONFIG_MBEDTLS mbedTLS)
Expand Down
17 changes: 16 additions & 1 deletion subsys/net/lib/http/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ config HTTP_CLIENT
help
HTTP client API

config HTTP_SERVER
menuconfig HTTP_SERVER
bool "HTTP Server [EXPERIMENTAL]"
select HTTP_PARSER
select HTTP_PARSER_URL
Expand Down Expand Up @@ -201,6 +201,21 @@ config WEBSOCKET_CONSOLE
help
Hidden option that is enabled only when all the necessary options
needed by websocket console are set.

config HTTP_SERVER_COMPRESSION
bool "Compression support in HTTP fileserver"
help
If enabled, the fileserver will parse the accept-encoding header
from the client and extract the supported compression methods.
Afterwards it will try to serve the first compressed file available
by using the file ending as compression indicator in this order:
1. brotli -> .br
2. gzip -> .gz
3. zstd -> .zst
4. compress -> .lzw
5. deflate -> .zz
6. File without compression

endif

# Hidden option to avoid having multiple individual options that are ORed together
Expand Down
11 changes: 10 additions & 1 deletion subsys/net/lib/http/headers/server_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,22 @@ int enter_http1_request(struct http_client_ctx *client);
int enter_http2_request(struct http_client_ctx *client);
int enter_http_done_state(struct http_client_ctx *client);

/* HTTP Compression handling */
#define HTTP_COMPRESSION_MAX_STRING_LEN 8
void http_compression_parse_accept_encoding(const char *accept_encoding, size_t len,
uint8_t *supported_compression);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please align to the opening bracket.

const char *http_compression_text(enum http_compression compression);
int http_compression_from_text(enum http_compression *compression, const char *text);

/* Others */
struct http_resource_detail *get_resource_detail(const struct http_service_desc *service,
const char *path, int *len, bool is_ws);
int http_server_sendall(struct http_client_ctx *client, const void *buf, size_t len);
void http_server_get_content_type_from_extension(char *url, char *content_type,
size_t content_type_size);
int http_server_find_file(char *fname, size_t fname_size, size_t *file_size, bool *gzipped);
int http_server_find_file(char *fname, size_t fname_size, size_t *file_size,
uint8_t supported_compression,
enum http_compression *chosen_compression);
void http_client_timer_restart(struct http_client_ctx *client);
bool http_response_is_final(struct http_response_ctx *rsp, enum http_data_status status);
bool http_response_is_provided(struct http_response_ctx *rsp);
Expand Down
134 changes: 134 additions & 0 deletions subsys/net/lib/http/http_compression.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/** @file
* @brief HTTP compression handling functions
*
* Helper functions to handle compression formats
*/

/*
* Copyright (c) 2025 Endress+Hauser
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>

#include <zephyr/net/http/server.h>
#include <zephyr/sys/util.h>

#include "headers/server_internal.h"

static bool compression_value_is_valid(enum http_compression compression);
static int http_compression_match(enum http_compression *compression, const char *text,
enum http_compression expected);

void http_compression_parse_accept_encoding(const char *accept_encoding, size_t len,
uint8_t *supported_compression)
{
enum http_compression detected_compression;
char strbuf[HTTP_COMPRESSION_MAX_STRING_LEN + 1] = {0};
const char *start = accept_encoding;
const char *end = NULL;
bool priority_string = false;

*supported_compression = HTTP_NONE;
for (size_t i = 0; i < len; i++) {
if (accept_encoding[i] == 0) {
break;
} else if (accept_encoding[i] == ' ') {
start = &accept_encoding[i + 1];
continue;
} else if (accept_encoding[i] == ';') {
end = &accept_encoding[i];
priority_string = true;
} else if (accept_encoding[i] == ',') {
if (!priority_string) {
end = &accept_encoding[i];
}
priority_string = false;
} else if (i + 1 == len) {
end = &accept_encoding[i + 1];
}

if (end == NULL) {
continue;
}

if (end - start > HTTP_COMPRESSION_MAX_STRING_LEN) {
end = NULL;
start = end + 1;
continue;
}

memcpy(strbuf, start, end - start);
strbuf[end - start] = 0;

if (http_compression_from_text(&detected_compression, strbuf) == 0) {
WRITE_BIT(*supported_compression, detected_compression, true);
}

end = NULL;
start = end + 1;
}
}

const char *http_compression_text(enum http_compression compression)
{
switch (compression) {
case HTTP_NONE:
return "";
case HTTP_GZIP:
return "gzip";
case HTTP_COMPRESS:
return "compress";
case HTTP_DEFLATE:
return "deflate";
case HTTP_BR:
return "br";
case HTTP_ZSTD:
return "zstd";
}
return "";
}

int http_compression_from_text(enum http_compression *compression, const char *text)
{
assert(compression);
assert(text);

for (enum http_compression i = 0; compression_value_is_valid(i); ++i) {
if (http_compression_match(compression, text, i) == 0) {
return 0;
}
}
return 1;
}

static bool compression_value_is_valid(enum http_compression compression)
{
switch (compression) {
case HTTP_NONE:
case HTTP_GZIP:
case HTTP_COMPRESS:
case HTTP_DEFLATE:
case HTTP_BR:
case HTTP_ZSTD:
return true;
}
return false;
}

static int http_compression_match(enum http_compression *compression, const char *text,
enum http_compression expected)
{
assert(compression);
assert(text);

if (strcasecmp(http_compression_text(expected), text) == 0) {
*compression = expected;
return 0;
} else {
return 1;
}
}
64 changes: 52 additions & 12 deletions subsys/net/lib/http/http_server_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <zephyr/net/tls_credentials.h>
#include <zephyr/posix/sys/eventfd.h>
#include <zephyr/posix/fnmatch.h>
#include <zephyr/sys/util_macro.h>

LOG_MODULE_REGISTER(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL);

Expand Down Expand Up @@ -785,26 +786,65 @@ struct http_resource_detail *get_resource_detail(const struct http_service_desc
return NULL;
}

int http_server_find_file(char *fname, size_t fname_size, size_t *file_size, bool *gzipped)
int http_server_find_file(char *fname, size_t fname_size, size_t *file_size,
uint8_t supported_compression, enum http_compression *chosen_compression)
{
struct fs_dirent dirent;
size_t len;
int ret;

ret = fs_stat(fname, &dirent);
if (ret < 0) {
len = strlen(fname);
snprintk(fname + len, fname_size - len, ".gz");
ret = fs_stat(fname, &dirent);
*gzipped = (ret == 0);
len = strlen(fname);
if (IS_ENABLED(CONFIG_HTTP_SERVER_COMPRESSION)) {
*chosen_compression = HTTP_NONE;
if (IS_BIT_SET(supported_compression, HTTP_BR)) {
snprintk(fname + len, fname_size - len, ".br");
ret = fs_stat(fname, &dirent);
if (ret == 0) {
*chosen_compression = HTTP_BR;
goto return_filename;
}
}
if (IS_BIT_SET(supported_compression, HTTP_GZIP)) {
snprintk(fname + len, fname_size - len, ".gz");
ret = fs_stat(fname, &dirent);
if (ret == 0) {
*chosen_compression = HTTP_GZIP;
goto return_filename;
}
}
if (IS_BIT_SET(supported_compression, HTTP_ZSTD)) {
snprintk(fname + len, fname_size - len, ".zst");
ret = fs_stat(fname, &dirent);
if (ret == 0) {
*chosen_compression = HTTP_ZSTD;
goto return_filename;
}
}
if (IS_BIT_SET(supported_compression, HTTP_COMPRESS)) {
snprintk(fname + len, fname_size - len, ".lzw");
ret = fs_stat(fname, &dirent);
if (ret == 0) {
*chosen_compression = HTTP_COMPRESS;
goto return_filename;
}
}
if (IS_BIT_SET(supported_compression, HTTP_DEFLATE)) {
snprintk(fname + len, fname_size - len, ".zz");
ret = fs_stat(fname, &dirent);
if (ret == 0) {
*chosen_compression = HTTP_DEFLATE;
goto return_filename;
}
}
}

if (ret == 0) {
*file_size = dirent.size;
return ret;
ret = fs_stat(fname, &dirent);
if (ret != 0) {
return -ENOENT;
}

return -ENOENT;
return_filename:
*file_size = dirent.size;
return ret;
}

void http_server_get_content_type_from_extension(char *url, char *content_type,
Expand Down
Loading
Loading