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 Feb 28, 2025
1 parent b74b092 commit f0533cf
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 28 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. */
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. */
bool accept_encoding_next: 1;

/** The next frame on the stream is expectd to be a continuation frame. */
bool expect_continuation : 1;
};
Expand Down
3 changes: 2 additions & 1 deletion subsys/net/lib/http/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ zephyr_library_sources_ifdef(CONFIG_HTTP_SERVER http_server_core.c
http_server_http1.c
http_server_http2.c
http_hpack.c
http_huffman.c)
http_huffman.c
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
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;
}
}
58 changes: 48 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,62 @@ 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);
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;
}
}
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

0 comments on commit f0533cf

Please sign in to comment.