From b9ffe8bd2712abaad93b2f3d705a497accecc17b Mon Sep 17 00:00:00 2001 From: Azreyo <58790873+Azreyo@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:57:52 +0000 Subject: [PATCH] Develop (#12) * Refactor carbon-server service in docker-compose.yml to use pre-built image and remove unnecessary build context and volume mounts * Enhance performance and security features: - Update compiler flags for better optimization and security. - Implement MIME type caching for improved response handling. - Introduce worker thread pool for efficient connection management. - Optimize socket settings for low latency and enhanced performance. - Add support for CPU affinity in worker threads. - Implement graceful shutdown for worker threads during cleanup. * Optimize HTTP response handling and increase request limits for improved performance * Add gzip compression support for HTTP responses --- Makefile | 2 +- src/server.c | 191 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 172 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 82e1caf..eab76a7 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ CFLAGS = -Wall -Wextra -Werror -O3 -march=native -mtune=native -flto -D_GNU_SOUR CFLAGS += -fPIE -fno-strict-overflow -Wformat -Wformat-security -Werror=format-security CFLAGS += -D_FORTIFY_SOURCE=2 -fvisibility=hidden LDFLAGS = -pthread -Wl,-z,relro,-z,now -pie -LIBS = -lssl -lcrypto -lmagic -lnghttp2 +LIBS = -lssl -lcrypto -lmagic -lnghttp2 -lz # Source files and object files SRCS = src/server.c src/config_parser.c src/server_config.c src/websocket.c src/http2.c src/performance.c diff --git a/src/server.c b/src/server.c index 09d3245..411ff3f 100644 --- a/src/server.c +++ b/src/server.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "server_config.h" #include "websocket.h" @@ -148,6 +149,9 @@ int parse_request_line(char *request_buffer, char *method, char *url, char *prot char *get_mime_type(const char *filepath); char *sanitize_url(const char *url); int check_rate_limit(const char *ip); +int should_compress(const char *mime_type); +unsigned char *gzip_compress(const unsigned char *data, size_t size, size_t *compressed_size); +char *stristr(const char *haystack, const char *needle); void initialize_openssl() { @@ -574,6 +578,10 @@ void *handle_http_client(void *arg) request_buffer[bytes_received] = '\0'; log_event("Received HTTP request"); + // Check if client accepts gzip BEFORE parsing (parse modifies buffer!) + int accepts_gzip = (stristr(request_buffer, "accept-encoding:") && + stristr(request_buffer, "gzip")) ? 1 : 0; + // Check for WebSocket upgrade request if (config.enable_websocket && is_websocket_upgrade(request_buffer)) { @@ -690,52 +698,89 @@ void *handle_http_client(void *arg) if (cached) { - // Serve from cache - optimized with writev for single syscall + // Check if we should compress + unsigned char *compressed_data = NULL; + size_t compressed_size = 0; + int using_compression = 0; + + char debug_msg[256]; + snprintf(debug_msg, sizeof(debug_msg), "accepts_gzip=%d, should_compress=%d, size=%zu", + accepts_gzip, should_compress(cached->mime_type), cached->size); + log_event(debug_msg); + + if (accepts_gzip && should_compress(cached->mime_type) && cached->size > 1024) + { + compressed_data = gzip_compress((unsigned char *)cached->mmap_data, cached->size, &compressed_size); + if (compressed_data && compressed_size < cached->size * 0.9) // Only use if 10%+ savings + { + using_compression = 1; + snprintf(debug_msg, sizeof(debug_msg), "Compression: %zu -> %zu bytes (%.1f%%)", + cached->size, compressed_size, (compressed_size * 100.0) / cached->size); + log_event(debug_msg); + } + else if (compressed_data) + { + log_event("Compression not efficient enough, skipping"); + free(compressed_data); + compressed_data = NULL; + } + } + + // Serve from cache with optional compression char response_header[2048]; int header_len = snprintf(response_header, sizeof(response_header), "HTTP/1.1 200 OK\r\n" "Content-Length: %zu\r\n" "Content-Type: %s\r\n" "Cache-Control: public, max-age=86400, immutable\r\n" - "ETag: \"%zu-%ld\"\r\n" + "ETag: \"%zu-%ld%s\"\r\n" "%s" + "%s" + "Keep-Alive: timeout=5, max=100\r\n" + "Connection: Keep-Alive\r\n" "\r\n", - cached->size, + using_compression ? compressed_size : cached->size, cached->mime_type, cached->size, cached->last_access, + using_compression ? "-gzip" : "", + using_compression ? "Content-Encoding: gzip\r\n" : "", SECURITY_HEADERS); + void *data_to_send = using_compression ? compressed_data : cached->mmap_data; + size_t size_to_send = using_compression ? compressed_size : cached->size; + // Use writev to send header + content in one syscall (for small files) - if (cached->size < 65536) // Files < 64KB + if (size_to_send < 65536) // Files < 64KB { struct iovec iov[2]; iov[0].iov_base = response_header; iov[0].iov_len = header_len; - iov[1].iov_base = cached->mmap_data; - iov[1].iov_len = cached->size; + iov[1].iov_base = data_to_send; + iov[1].iov_len = size_to_send; ssize_t written = writev(client_socket, iov, 2); - (void)written; // Mark as used to avoid warning + (void)written; } else { - // Send header first, then data in chunks for larger files send(client_socket, response_header, header_len, 0); size_t total_sent = 0; - while (total_sent < cached->size) + while (total_sent < size_to_send) { - ssize_t sent = send(client_socket, (char *)cached->mmap_data + total_sent, - cached->size - total_sent, 0); + ssize_t sent = send(client_socket, (char *)data_to_send + total_sent, + size_to_send - total_sent, 0); if (sent <= 0) break; total_sent += sent; } } + if (compressed_data) + free(compressed_data); release_cached_file(cached); free(mime_type); - log_event("Served file from cache"); + log_event(using_compression ? "Served file from cache (gzip)" : "Served file from cache"); goto done_serving; } @@ -978,6 +1023,10 @@ void *handle_https_client(void *arg) log_event(protocol); } + // Check if client accepts gzip (case-insensitive) + int accepts_gzip = (stristr(buffer, "accept-encoding:") && + stristr(buffer, "gzip")) ? 1 : 0; + char *sanitized_url = sanitize_url(url); if (!sanitized_url) { @@ -1025,38 +1074,67 @@ void *handle_https_client(void *arg) if (cached) { - // Serve from cache + // Check if we should compress + unsigned char *compressed_data = NULL; + size_t compressed_size = 0; + int using_compression = 0; + + if (accepts_gzip && should_compress(cached->mime_type) && cached->size > 1024) + { + compressed_data = gzip_compress((unsigned char *)cached->mmap_data, cached->size, &compressed_size); + if (compressed_data && compressed_size < cached->size * 0.9) + { + using_compression = 1; + } + else if (compressed_data) + { + free(compressed_data); + compressed_data = NULL; + } + } + + // Serve from cache with optional compression char response_header[2048]; int header_len = snprintf(response_header, sizeof(response_header), "HTTP/1.1 200 OK\r\n" "Content-Length: %zu\r\n" "Content-Type: %s\r\n" "Cache-Control: public, max-age=86400, immutable\r\n" - "ETag: \"%zu-%ld\"\r\n" + "ETag: \"%zu-%ld%s\"\r\n" "%s" + "%s" + "Keep-Alive: timeout=5, max=100\r\n" + "Connection: Keep-Alive\r\n" "\r\n", - cached->size, + using_compression ? compressed_size : cached->size, cached->mime_type, cached->size, cached->last_access, + using_compression ? "-gzip" : "", + using_compression ? "Content-Encoding: gzip\r\n" : "", SECURITY_HEADERS); SSL_write(ssl, response_header, header_len); - // Send cached data + // Send compressed or uncompressed data + void *data_to_send = using_compression ? compressed_data : cached->mmap_data; + size_t size_to_send = using_compression ? compressed_size : cached->size; + size_t total_sent = 0; - while (total_sent < cached->size) + while (total_sent < size_to_send) { - int to_send = (cached->size - total_sent > 65536) ? 65536 : (cached->size - total_sent); - int sent = SSL_write(ssl, (char *)cached->mmap_data + total_sent, to_send); + int to_send = (size_to_send - total_sent > 65536) ? 65536 : (size_to_send - total_sent); + int sent = SSL_write(ssl, (char *)data_to_send + total_sent, to_send); if (sent <= 0) break; total_sent += sent; } + if (compressed_data) + free(compressed_data); release_cached_file(cached); free(mime_type); - log_event("Served file from cache (mmap)"); + log_event(using_compression ? "Served file from cache (gzip)" : "Served file from cache (mmap)"); goto cleanup; } @@ -1409,6 +1487,79 @@ void initialize_thread_pool() log_event(msg); } +// Case-insensitive strstr +char *stristr(const char *haystack, const char *needle) +{ + if (!haystack || !needle) return NULL; + + size_t needle_len = strlen(needle); + if (needle_len == 0) return (char *)haystack; + + for (const char *p = haystack; *p; p++) + { + if (tolower(*p) == tolower(*needle)) + { + size_t i; + for (i = 1; i < needle_len && p[i]; i++) + { + if (tolower(p[i]) != tolower(needle[i])) + break; + } + if (i == needle_len) + return (char *)p; + } + } + return NULL; +} + +// Check if MIME type should be compressed +int should_compress(const char *mime_type) +{ + return (strstr(mime_type, "text/") != NULL || + strstr(mime_type, "application/javascript") != NULL || + strstr(mime_type, "application/json") != NULL || + strstr(mime_type, "application/xml") != NULL); +} + +// Gzip compress data +unsigned char *gzip_compress(const unsigned char *data, size_t size, size_t *compressed_size) +{ + z_stream stream = {0}; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + + if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) + { + return NULL; + } + + size_t max_compressed = deflateBound(&stream, size); + unsigned char *compressed = malloc(max_compressed); + if (!compressed) + { + deflateEnd(&stream); + return NULL; + } + + stream.avail_in = size; + stream.next_in = (unsigned char *)data; + stream.avail_out = max_compressed; + stream.next_out = compressed; + + if (deflate(&stream, Z_FINISH) != Z_STREAM_END) + { + free(compressed); + deflateEnd(&stream); + return NULL; + } + + *compressed_size = stream.total_out; + deflateEnd(&stream); + + return compressed; +} + int main() { if (load_config("server.conf", &config) != 0)