Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
21be7f5
feat(compression): add response compression API
noneandundefined Sep 20, 2026
8fc7888
feat(compression): add provider-based compression middleware
noneandundefined Sep 20, 2026
aff2524
feat(compression): allow per-response opt-out
noneandundefined Sep 20, 2026
8f6a5f6
feat(compression): add server and route compression state
noneandundefined Sep 20, 2026
b88f0f4
feat(compression): clean compression state on shutdown
noneandundefined Sep 20, 2026
595d7a0
feat(compression): honor route compression opt-out
noneandundefined Sep 20, 2026
5a31647
feat(api): expose response compression
noneandundefined Sep 20, 2026
90d53ff
build(compression): add optional zlib gzip build
noneandundefined Sep 20, 2026
b43fe4c
build(compression): add zlib pkg-config metadata
noneandundefined Sep 20, 2026
4c2d195
build(compression): add TLS and zlib pkg-config metadata
noneandundefined Sep 20, 2026
32202ce
test(compression): cover gzip negotiation and response policy
noneandundefined Sep 20, 2026
80d71e9
docs(compression): add gzip server example
noneandundefined Sep 20, 2026
9f01ef2
perf(compression): add gzip CPU bandwidth benchmark
noneandundefined Sep 20, 2026
7d6222f
fix(compression): parse encoding quality values
noneandundefined Sep 20, 2026
64b5ab3
docs(compression): add English guide
noneandundefined Sep 20, 2026
94e0802
docs(compression): add Russian guide
noneandundefined Sep 20, 2026
62488d9
docs(compression): add module to documentation index
noneandundefined Sep 20, 2026
31eec9e
docs(compression): add module to Russian index
noneandundefined Sep 20, 2026
c35637e
docs(compression): document optional gzip build
noneandundefined Sep 20, 2026
b200863
docs(compression): document optional gzip build in Russian
noneandundefined Sep 20, 2026
09f10ba
ci(compression): test zlib response compression
noneandundefined Sep 20, 2026
7bbad3c
refactor(compression): keep custom provider levels generic
noneandundefined Sep 20, 2026
e362a78
build(compression): make zlib part of the standard build
noneandundefined Sep 20, 2026
bef483b
refactor(compression): make gzip provider always available
noneandundefined Sep 20, 2026
5ef47f0
docs(compression): remove build-time gzip wording
noneandundefined Sep 20, 2026
1d8d8b9
build(pkgconfig): declare zlib dependency
noneandundefined Sep 20, 2026
0bc3715
build(pkgconfig): link zlib with TLS builds
noneandundefined Sep 20, 2026
86142aa
ci(compression): test gzip in standard builds
noneandundefined Sep 20, 2026
38c0fd7
build(packaging): include zlib dependency
noneandundefined Sep 20, 2026
f3bc01a
build(packaging): declare zlib runtime dependency
noneandundefined Sep 20, 2026
c059efe
build(docker): include zlib
noneandundefined Sep 20, 2026
3ddb6dc
build(installer): install zlib dependency
noneandundefined Sep 20, 2026
272d69d
build(windows): bundle zlib runtime when available
noneandundefined Sep 20, 2026
96b67ea
docs(compression): describe runtime-only enablement
noneandundefined Sep 20, 2026
3af0aa8
docs(compression): describe runtime enablement in Russian
noneandundefined Sep 20, 2026
634595f
docs(compression): remove separate gzip build mode
noneandundefined Sep 20, 2026
df7e46b
docs(compression): remove separate gzip build mode in Russian
noneandundefined Sep 20, 2026
adc478d
docs(compression): update example error message
noneandundefined Sep 20, 2026
c1208ed
build(compression): remove obsolete compression pkg-config variant
noneandundefined Sep 20, 2026
3c6714f
build(compression): remove obsolete TLS compression pkg-config variant
noneandundefined Sep 20, 2026
ca45c9d
ci(codeql): install zlib dependency
noneandundefined Sep 20, 2026
df8ccca
refactor(compression): remove redundant gzip availability API
noneandundefined Sep 20, 2026
17c2081
refactor(compression): remove redundant gzip availability declaration
noneandundefined Sep 20, 2026
f5987fb
refactor(api): remove redundant gzip availability helper
noneandundefined Sep 20, 2026
c8f8686
test(compression): assume standard gzip support
noneandundefined Sep 20, 2026
e999826
docs(compression): remove obsolete build flag name in Russian
noneandundefined Sep 20, 2026
e616281
docs(compression): remove obsolete build flag name
noneandundefined Sep 20, 2026
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
9 changes: 5 additions & 4 deletions .github/actions/build-linux-packages/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ runs:
bash -euc '
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends build-essential libcjson-dev
apt-get install -y --no-install-recommends build-essential libcjson-dev zlib1g-dev
make clean
make libchttpx.so
cp libchttpx.so dist/deb/libchttpx.so
Expand All @@ -47,7 +47,7 @@ runs:
-w /work \
fedora:42 \
bash -euc '
dnf install -y gcc make cjson-devel
dnf install -y gcc make cjson-devel zlib-devel
make clean
make libchttpx.so
cp libchttpx.so dist/rpm/libchttpx.so
Expand All @@ -62,7 +62,7 @@ runs:
-w /work \
archlinux:base-devel \
bash -euc '
pacman -Sy --noconfirm cjson
pacman -Sy --noconfirm cjson zlib
make clean
make libchttpx.so
cp libchttpx.so dist/archlinux/libchttpx.so
Expand All @@ -77,7 +77,7 @@ runs:
-w /work \
alpine:3.22 \
sh -euc '
apk add --no-cache build-base cjson-dev
apk add --no-cache build-base cjson-dev zlib-dev
make clean
make libchttpx.so
cp libchttpx.so dist/apk/libchttpx.so
Expand Down Expand Up @@ -109,6 +109,7 @@ runs:
"Description: A compact cross-platform HTTP/1.1 server library for C/C++.",
f"Version: {version}",
"Libs: -L${libdir} -lchttpx",
"Libs.private: -lz",
"Cflags: -I${includedir}/libchttpx",
"",
])
Expand Down
34 changes: 32 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y gcc make libcjson-dev clang-format
sudo apt install -y gcc make libcjson-dev zlib1g-dev clang-format

- name: Build binary
run: make lin
Expand All @@ -47,7 +47,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y gcc make libcjson-dev libssl-dev openssl
sudo apt install -y gcc make libcjson-dev zlib1g-dev libssl-dev openssl

- name: Build TLS-enabled shared library
run: make TLS=1 libchttpx.so
Expand All @@ -58,6 +58,35 @@ jobs:
- name: Run HTTPS integration test
run: make test-tls

build-linux-compression:
name: Response compression integration (Linux, zlib)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt update
sudo apt install -y gcc make libcjson-dev zlib1g-dev libssl-dev

- name: Build shared library
run: make libchttpx.so

- name: Build compression example
run: make examples-compression

- name: Run compression integration tests
run: make test-compression

- name: Run compression benchmark
run: make benchmark-compression

- name: Verify TLS build keeps compression support
run: |
make clean
make TLS=1 libchttpx.so

build-windows:
name: Build (Windows)
runs-on: windows-latest
Expand All @@ -76,6 +105,7 @@ jobs:
install: >-
mingw-w64-x86_64-gcc
mingw-w64-x86_64-make
mingw-w64-x86_64-zlib

- name: Build
run: mingw32-make win SHELL=cmd
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y gcc make libcjson-dev
sudo apt install -y gcc make libcjson-dev zlib1g-dev

- name: Build
run: make libchttpx.so
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
libcjson-dev \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
Expand All @@ -18,6 +19,7 @@ FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libcjson1 \
zlib1g \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /pkg/usr/local/lib/libchttpx.so /usr/local/lib/
Expand Down
25 changes: 22 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ PKGDIR ?= /pkg/usr/local

WIN_LIB_DIR = tools

LIN_LDFLAGS = -lcjson $(TLS_LDFLAGS)
WIN_LDFLAGS = -lws2_32 $(TLS_LDFLAGS)
LIN_LDFLAGS = -lcjson -lz $(TLS_LDFLAGS)
WIN_LDFLAGS = -lws2_32 -lz $(TLS_LDFLAGS)
TEST_TARGET = $(BINDIR)/test_core
TEST_SERVER_TARGET = $(BINDIR)/test_server
TEST_SANITIZE_TARGET = $(BINDIR)/test_core_sanitize
TEST_SERVER_SANITIZE_TARGET = $(BINDIR)/test_server_sanitize
TEST_TLS_TARGET = $(BINDIR)/test_tls
TEST_COMPRESSION_TARGET = $(BINDIR)/test_compression
COMPRESSION_BENCHMARK_TARGET = $(BINDIR)/benchmark-compression
EXAMPLE_SRC = example/basic.c
EXAMPLE_OBJ = $(OBJDIR)/example/basic.o

Expand Down Expand Up @@ -73,6 +75,8 @@ $(BINDIR)/example-%: example/%.c $(LIN_SRCS)
examples-tls:
@$(MAKE) TLS=1 $(BINDIR)/example-tls

examples-compression: $(BINDIR)/example-compression

# WINdows build
# -

Expand Down Expand Up @@ -108,12 +112,13 @@ lib-install: libchttpx.so
win-lib:
@echo "Building Windows DLL..."
if not exist $(BINDIR) mkdir $(BINDIR)
$(CC) $(CFLAGS) -std=c11 -D_WIN32 -shared -o $(BINDIR)/$(TARGET_DLL) $(WIN_SRCS) -Wl,--out-implib,$(BINDIR)/libchttpx.a -lws2_32
$(CC) $(CFLAGS) -std=c11 -D_WIN32 -shared -o $(BINDIR)/$(TARGET_DLL) $(WIN_SRCS) -Wl,--out-implib,$(BINDIR)/libchttpx.a $(WIN_LDFLAGS)

@echo "Copying files to $(WIN_LIB_DIR)..."
if not exist $(WIN_LIB_DIR) mkdir $(WIN_LIB_DIR)
copy /Y $(BINDIR)\$(TARGET_DLL) $(WIN_LIB_DIR)\$(TARGET_DLL)
copy /Y $(BINDIR)\libchttpx.a $(WIN_LIB_DIR)\libchttpx.a
for /f "delims=" %%i in ('where zlib1.dll 2^>nul') do copy /Y "%%i" $(WIN_LIB_DIR)\zlib1.dll
if not exist $(WIN_LIB_DIR)\include mkdir $(WIN_LIB_DIR)\include
xcopy /E /I /Y include $(WIN_LIB_DIR)\include

Expand Down Expand Up @@ -160,6 +165,20 @@ $(TEST_TLS_TARGET): tests/test_tls.c $(LIN_SRCS)
@mkdir -p $(BINDIR)
$(CC) $(CFLAGS) -std=gnu11 -g tests/test_tls.c $(LIN_SRCS) -o $@ $(LIN_LDFLAGS) -pthread

test-compression: $(TEST_COMPRESSION_TARGET)
$(TEST_COMPRESSION_TARGET)

$(TEST_COMPRESSION_TARGET): tests/test_compression.c $(LIN_SRCS)
@mkdir -p $(BINDIR)
$(CC) $(CFLAGS) -std=gnu11 -g tests/test_compression.c $(LIN_SRCS) -o $@ $(LIN_LDFLAGS) -pthread

benchmark-compression: $(COMPRESSION_BENCHMARK_TARGET)
$(COMPRESSION_BENCHMARK_TARGET)

$(COMPRESSION_BENCHMARK_TARGET): benchmarks/compression.c $(LIN_SRCS)
@mkdir -p $(BINDIR)
$(CC) $(CFLAGS) -std=gnu11 -O2 benchmarks/compression.c $(LIN_SRCS) -o $@ $(LIN_LDFLAGS) -pthread

# LINux lib compile
# -

Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The library is designed so handlers contain application logic instead of repetit
- multipart forms, multiple uploads, upload policies, and temporary-file cleanup
- request IDs and `Accept-Language` negotiation
- CORS, cookies, logging callbacks, and rate limiting
- optional gzip response compression with `Accept-Encoding` negotiation
- configurable server limits and graceful shutdown

> `cHTTPX_ResFile()` currently reads the complete file into memory. Streaming responses, `sendfile()`, and zero-copy output are not implemented in the current API.
Expand Down Expand Up @@ -124,11 +125,11 @@ CMD ["/usr/local/bin/my-server"]

### Build from source on Linux

Requirements: GCC, Make, pkg-config, and cJSON development files.
Requirements: GCC, Make, pkg-config, cJSON development files, and zlib development files.

```bash
sudo apt update
sudo apt install -y build-essential pkg-config libcjson-dev
sudo apt install -y build-essential pkg-config libcjson-dev zlib1g-dev

git clone https://github.com/netcorelink/libchttpx.git
cd libchttpx
Expand All @@ -154,9 +155,18 @@ make test-tls

See [Native TLS / HTTPS](docs/tls/README.md) for server certificates, HTTPS remote calls, custom CA verification, and mTLS.

Response compression is included in the standard build. zlib is a normal libchttpx dependency; applications enable compression at runtime with `cHTTPX_CompressionUse()` and configure minimum size, level, MIME policy, or custom providers in code.

```bash
make test-compression
make benchmark-compression
```

TLS remains independently optional: `make TLS=1 libchttpx.so`. See [Response compression](docs/compression/README.md).

### Build from source on Windows

Use MinGW/GCC. The Windows build uses the bundled `lib/cjson` source.
Use MinGW/GCC. The Windows build uses the bundled `lib/cjson` source and requires zlib (for MSYS2/MinGW64: `mingw-w64-x86_64-zlib`).

```powershell
git clone https://github.com/netcorelink/libchttpx.git
Expand All @@ -176,6 +186,7 @@ Detailed documentation is split by functionality:
- [App runtime, multiple servers, AppRemote, Call and CallEx](docs/app/README.md)
- [Server configuration, limits, lifecycle and error codes](docs/server/README.md)
- [Native TLS / HTTPS](docs/tls/README.md)
- [Response compression](docs/compression/README.md)
- [Routing and route groups](docs/routing/README.md)
- [Middleware](docs/middleware/README.md)
- [Requests, headers, params, queries and body access](docs/request/README.md)
Expand Down
15 changes: 13 additions & 2 deletions README_RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- multipart forms, несколько файлов, upload policy и автоматическое удаление временных файлов
- request ID и `Accept-Language` negotiation
- CORS, cookies, callback-based logging и rate limiting
- optional gzip-сжатие ответов с `Accept-Encoding` negotiation
- лимиты сервера и graceful shutdown

> `cHTTPX_ResFile()` пока полностью читает файл в память. Streaming response, `sendfile()` и zero-copy output в текущем API не реализованы.
Expand Down Expand Up @@ -124,7 +125,7 @@ CMD ["/usr/local/bin/my-server"]

```bash
sudo apt update
sudo apt install -y build-essential pkg-config libcjson-dev
sudo apt install -y build-essential pkg-config libcjson-dev zlib1g-dev

git clone https://github.com/netcorelink/libchttpx.git
cd libchttpx
Expand All @@ -150,9 +151,18 @@ make test-tls

Настройка server certificate, HTTPS remote calls, custom CA и mTLS описана в [Native TLS / HTTPS](docs/tls/README_RU.md).

Сжатие ответов входит в обычную сборку libchttpx. zlib — стандартная зависимость библиотеки, а включение gzip выполняется уже в коде приложения через `cHTTPX_CompressionUse()`. Размер, level, MIME policy и providers тоже задаются через config.

```bash
make test-compression
make benchmark-compression
```

TLS остаётся отдельной опцией сборки: `make TLS=1 libchttpx.so`. Подробнее: [Сжатие HTTP-ответов](docs/compression/README_RU.md).

### Самостоятельная сборка на Windows

Используется MinGW/GCC. Для Windows cJSON уже находится в `lib/cjson`.
Используется MinGW/GCC. Для Windows cJSON уже находится в `lib/cjson`, а zlib должен быть установлен (для MSYS2/MinGW64: `mingw-w64-x86_64-zlib`).

```powershell
git clone https://github.com/netcorelink/libchttpx.git
Expand All @@ -172,6 +182,7 @@ DLL, import library и headers копируются в `tools/`.
- [App runtime, несколько серверов, AppRemote, Call и CallEx](docs/app/README_RU.md)
- [Конфигурация сервера, лимиты, lifecycle и error codes](docs/server/README_RU.md)
- [Native TLS / HTTPS](docs/tls/README_RU.md)
- [Сжатие HTTP-ответов](docs/compression/README_RU.md)
- [Routing и route groups](docs/routing/README_RU.md)
- [Middleware](docs/middleware/README_RU.md)
- [Requests, headers, params, queries и body](docs/request/README_RU.md)
Expand Down
107 changes: 107 additions & 0 deletions benchmarks/compression.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <zlib.h>

#define PAYLOAD_SIZE (1024 * 1024)
#define ITERATIONS 100

static double elapsed_seconds(struct timespec start, struct timespec end)
{
return (double)(end.tv_sec - start.tv_sec) + (double)(end.tv_nsec - start.tv_nsec) / 1000000000.0;
}

static void build_payload(unsigned char* payload, size_t size)
{
static const char row[] =
"{\"id\":12345,\"username\":\"artem\",\"active\":true,"
"\"message\":\"response compression benchmark payload\"}\n";

size_t offset = 0;
while (offset < size)
{
size_t remaining = size - offset;
size_t chunk = sizeof(row) - 1;
if (chunk > remaining)
chunk = remaining;
memcpy(payload + offset, row, chunk);
offset += chunk;
}
}

static int gzip_once(const unsigned char* input, size_t input_size, int level, unsigned char* output, size_t output_capacity, size_t* output_size)
{
z_stream stream;
memset(&stream, 0, sizeof(stream));

if (deflateInit2(&stream, level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
return 0;

stream.next_in = (Bytef*)input;
stream.avail_in = (uInt)input_size;
stream.next_out = output;
stream.avail_out = (uInt)output_capacity;

int result = deflate(&stream, Z_FINISH);
if (result != Z_STREAM_END)
{
deflateEnd(&stream);
return 0;
}

*output_size = output_capacity - stream.avail_out;
deflateEnd(&stream);
return 1;
}

int main(void)
{
unsigned char* payload = malloc(PAYLOAD_SIZE);
if (!payload)
return 1;
build_payload(payload, PAYLOAD_SIZE);

uLong bound = compressBound(PAYLOAD_SIZE) + 64;
unsigned char* compressed = malloc((size_t)bound);
if (!compressed)
{
free(payload);
return 1;
}

const int levels[] = {1, 5, 9};
puts("level,input_bytes,compressed_bytes,ratio,throughput_mib_s");

for (size_t level_index = 0; level_index < sizeof(levels) / sizeof(levels[0]); level_index++)
{
int level = levels[level_index];
size_t compressed_size = 0;

struct timespec start;
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &start);

for (int iteration = 0; iteration < ITERATIONS; iteration++)
{
if (!gzip_once(payload, PAYLOAD_SIZE, level, compressed, (size_t)bound, &compressed_size))
{
free(compressed);
free(payload);
return 1;
}
}

clock_gettime(CLOCK_MONOTONIC, &end);
double seconds = elapsed_seconds(start, end);
double mib = ((double)PAYLOAD_SIZE * ITERATIONS) / (1024.0 * 1024.0);
double ratio = (double)compressed_size / (double)PAYLOAD_SIZE;

printf("%d,%d,%zu,%.4f,%.2f\n", level, PAYLOAD_SIZE, compressed_size, ratio, mib / seconds);
}

free(compressed);
free(payload);
return 0;
}
Loading
Loading