Skip to content

Commit

Permalink
net: http: Add compression support in HTTP server
Browse files Browse the repository at this point in the history
Add compression support using the accept-encoding
header to the http server static filesystem resource.

Signed-off-by: Carlo Kirchmeier <carlo.kirchmeier@zuehlke.com>
  • Loading branch information
kica-z committed Mar 4, 2025
1 parent b74b092 commit 7271508
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 29 deletions.
54 changes: 54 additions & 0 deletions include/zephyr/net/http/compression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/** @file
* @brief HTTP compressions
*/

/*
* Copyright (c) 2025 Endress+Hauser
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_NET_HTTP_COMPRESSION_H_
#define ZEPHYR_INCLUDE_NET_HTTP_COMPRESSION_H_

/**
* @brief HTTP compressions
* @defgroup http_compressions HTTP compressions
* @since 4.2
* @version 0.1.0
* @ingroup networking
* @{
*/

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stddef.h>

#define HTTP_COMPRESSION_MAX_STRING_LEN 8

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

void http_compression_parse_accept_encoding(const char *accept_encoding, size_t len,
uint8_t *supported_compression);
const char *http_compression_text(enum http_compression compression);
int http_compression_from_text(enum http_compression compression[static 1], const char *text);

#ifdef __cplusplus
}
#endif

/**
* @}
*/

#endif /* ZEPHYR_INCLUDE_NET_HTTP_COMPRESSION_H_ */
8 changes: 8 additions & 0 deletions include/zephyr/net/http/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,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 +498,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
5 changes: 4 additions & 1 deletion subsys/net/lib/http/headers/server_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <zephyr/net/http/status.h>
#include <zephyr/net/http/hpack.h>
#include <zephyr/net/http/frame.h>
#include <zephyr/net/http/compression.h>

/* HTTP1/HTTP2 state handling */
int handle_http_frame_rst_stream(struct http_client_ctx *client);
Expand Down Expand Up @@ -42,7 +43,9 @@ struct http_resource_detail *get_resource_detail(const struct http_service_desc
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
129 changes: 129 additions & 0 deletions subsys/net/lib/http/http_compression.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/** @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/compression.h>
#include <zephyr/sys/util.h>

static bool compression_value_is_valid(enum http_compression compression);
static int http_compression_match(enum http_compression compression[static 1], 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 = 0;
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_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[static 1], 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_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[static 1], 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;
}
}
63 changes: 53 additions & 10 deletions subsys/net/lib/http/http_server_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/http/service.h>
#include <zephyr/net/http/compression.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/socket.h>
#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 +787,67 @@ 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);
len = strlen(fname);
#ifdef CONFIG_HTTP_SERVER_COMPRESSION
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);
*gzipped = (ret == 0);
if (ret == 0) {
*chosen_compression = HTTP_GZIP;
goto return_filename;
}
}

if (ret == 0) {
*file_size = dirent.size;
return ret;
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;
}
}
#endif /* CONFIG_HTTP_SERVER_COMPRESSION */
ret = fs_stat(fname, &dirent);
if (ret != 0) {
return -ENOENT;
}


return -ENOENT;
#ifdef CONFIG_HTTP_SERVER_COMPRESSION
return_filename:
#endif /* CONFIG_HTTP_SERVER_COMPRESSION */
*file_size = dirent.size;
return ret;
}

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

0 comments on commit 7271508

Please sign in to comment.