statico 0.5.1

A High-performance HTTP webserver that serves static responses at the speed of light.
statico-0.5.1 is not a library.

Statico

A high-performance HTTP webserver implemented in Rust that serves static responses at the speed of light. Its sole purpose is to handle HTTP requests as fast as possible; as such, it is neither a full-featured HTTP server nor an HTTP router, and it is not intended for production use beyond benchmarking.

Features

  • Multi-threaded: Configurable number of worker threads
  • Single-threaded Tokio runtime per thread: Each worker thread runs its own Tokio current-thread runtime
  • SO_REUSEPORT: Multiple threads can bind to the same port for load balancing
  • Configurable responses: Set custom HTTP status codes, headers, and response body
  • File-based responses: Load response body from files using @filename syntax
  • Optional io_uring support: Experimental support for io_uring on Linux (compile-time feature)
  • Cross-platform: Works on Linux, macOS, Windows, and other Unix-like systems

Performance

The following benchmark compares Statico against other popular HTTP servers and frameworks in a synthetic scenario where each server returns a minimal static response from memory. All servers were configured for maximum performance (no logging, CPU pinning where applicable, in-memory responses).

Performance Benchmark

Benchmark Results (requests/second)

Server 1 thread 2 threads 4 threads
statico + io_uring 270,263 593,067 1,138,319
statico 279,842 441,117 966,248
nginx (return) 286,960 379,974 832,082
HAProxy 181,127 253,796 515,162
Go net/http 69,212 168,220 366,084
Go fasthttp 172,359 273,395 605,603
Axum (Rust) 121,680 224,712 414,640
actix-web (Rust) 213,756 343,037 798,809

Notes on the benchmark setup

  • nginx: Configured for maximum performance with logging disabled, CPU pinning, and returning a response directly from an in-memory buffer (no disk I/O).
  • HAProxy: Same optimizations as nginx — no logging, CPU pinning, in-memory response.
  • Go net/http: A minimal HTTP server using Go's standard library, always returning 200 OK.
  • Go fasthttp: Same as net/http but using the high-performance fasthttp library.
  • Axum: A Rust web framework example using a single multi-threaded Tokio runtime (with 1, 2, or 4 worker threads).
  • actix-web: A Rust web framework example with configurable thread count (with 1, 2, or 4 threads).

Key observations

  1. Statico with io_uring scales exceptionally well — at 4 threads it achieves over 1.1 million req/s, outperforming all other solutions by a significant margin.
  2. Single-thread performance: Standard Statico and nginx perform similarly (~280k req/s), but Statico pulls ahead as thread count increases due to better multi-core scaling.
  3. Multi-thread scaling: Statico with io_uring shows near-linear scaling (2.1x at 2 threads, 4.2x at 4 threads), while nginx and others show diminishing returns.
  4. Rust frameworks comparison: Statico outperforms both Axum and actix-web significantly, demonstrating the benefit of its specialized architecture (per-thread Tokio runtimes + SO_REUSEPORT).
  5. Go comparison: Even fasthttp, known for its performance, reaches only ~53% of Statico+io_uring throughput at 4 threads.

Note: The "statico + io_uring" results in the benchmark refer to the tokio-uring runtime (enabled with --runtime tokio-uring). Statico also supports other io_uring-based runtimes such as monoio and glommio, which may show even better performance figures. An updated benchmark will be published once the codebase reaches sufficient stability.

Why is Statico fast?

  • Minimal request processing overhead
  • Efficient multi-threading with SO_REUSEPORT load balancing
  • Single-threaded Tokio runtimes reduce context switching
  • Zero-allocation response serving (responses are pre-built and cached)
  • File content loaded once at startup for optimal performance
  • Use io_uring, achieving up to 40% better performance than the basic version on Linux

Building

Standard build:

cargo build --release

With io_uring support (Linux only):

cargo build --release --features io_uring

Usage

./target/release/statico [OPTIONS]

Command Line Options

Option Description
-t, --threads <THREADS> Number of worker threads to spawn (default: number of CPUs)
-p, --port <PORT> Port to listen on (default: 8080)
-a, --address <ADDRESS> Address to listen on. If not specified, listen on all interfaces
-s, --status <STATUS> HTTP status code to return (default: 200)
-b, --body <BODY> Response body content (optional). Use @filename to load from file
-H, --header <HEADER> Custom headers in "Name: Value" format (can be specified multiple times)
-d, --delay <DELAY> Delay before sending the response (e.g., 100ms, 1s, 500us)
--body-delay <DELAY> Delay before sending the body of the response (e.g., 100ms, 1s, 500us)
-m, --meter Enable real-time metrics monitoring (requests/sec, bandwidth)
-v, --verbose Increase verbosity level (can be repeated: -v, -vv, -vvv, -vvvv)
--http2 Enable HTTP/2 (h2c) support
--runtime <RUNTIME> Runtime to use: tokio, tokio-local, tokio-uring, monoio, glommio (default: tokio)
--receive-buffer-size <SIZE> Receive buffer size
--send-buffer-size <SIZE> Send buffer size
--listen-backlog <SIZE> Listen backlog queue
--tcp-nodelay Set TCP_NODELAY option
--uring-entries <SIZE> Size of the io_uring Submission Queue (SQ) (default: 4096, Linux only)
--uring-sqpoll <MS> Enable kernel-side submission polling with idle timeout in milliseconds (Linux only)
-h, --help Print help
-V, --version Print version

Examples

Basic usage

Start a server on port 8080 with default settings:

./target/release/statico

Custom port and threads

./target/release/statico --port 3000 --threads 4

Serve custom content with headers

./target/release/statico \
  --status 201 \
  --body "Hello, World!" \
  --header "Content-Type: text/plain" \
  --header "X-Custom-Header: MyValue"

Serve JSON response

./target/release/statico \
  --status 200 \
  --body '{"message": "Hello from Statico!", "timestamp": "2024-01-01T00:00:00Z"}' \
  --header "Content-Type: application/json" \
  --header "Cache-Control: no-cache"

Error response simulation

./target/release/statico \
  --status 404 \
  --body "Not Found" \
  --header "Content-Type: text/plain"

Load testing setup

# Start server with many threads for high concurrency
./target/release/statico \
  --threads 16 \
  --port 8080 \
  --body "OK" \
  --header "Content-Type: text/plain"

With io_uring runtimes (Linux only)

# Using tokio-uring (requires compilation with --features tokio_uring)
./target/release/statico --runtime tokio-uring --threads 8

# Using monoio (requires compilation with --features monoio)
./target/release/statico --runtime monoio --threads 8

# Using glommio (requires compilation with --features glommio)
./target/release/statico --runtime glommio --threads 8

With response delay (latency simulation)

# Add 100ms delay before each response
./target/release/statico --delay 100ms --body "Delayed response"

# Add 1 second delay
./target/release/statico --delay 1s

Verbose mode (request/response logging)

The -v flag controls verbosity level. Each additional v increases the detail:

Level Flag Description
0 (none) No output
1 -v Request/status line only
2 -vv Request/status line + headers
3 -vvv Request/status line + headers + body (non-printable as hex)
4+ -vvvv Request/status line + headers + body (full hexdump)

When displaying the body, printable ASCII characters are shown as-is, while non-printable bytes are displayed as reversed hex codes.

# No request/response output
./target/release/statico

# Show request/status line only
./target/release/statico -v

# Show request/status line + headers
./target/release/statico -vv

# Show request/status line + headers + body (non-printable as hex)
./target/release/statico -vvv

# Show request/status line + headers + body (full hexdump)
./target/release/statico -vvvv

Example: Request with non-printable bytes in body

When a request body contains non-printable characters (e.g., binary data), they are displayed differently depending on verbosity level.

# Start server with verbose body output
./target/release/statico -vvv --body "OK"

# In another terminal, send a request with binary data:
curl -X POST --data $'Hello\x00World\x01!' http://localhost:8080

Output with -vvv (inline hex for non-printable bytes):

↩ request:
POST / HTTP/1.1
host: localhost:8080
content-type: application/x-www-form-urlencoded

Hello00World01!

The 00 and 01 are the hex representations of the null byte (\x00) and SOH (\x01), displayed with reversed colors to distinguish them from regular text.

Output with -vvvv (full hexdump format):

↩ request:
POST / HTTP/1.1
host: localhost:8080
content-type: application/x-www-form-urlencoded

00000000  48 65 6c 6c 6f 00 57 6f  72 6c 64 01 21           |Hello.World.!|

The hexdump format shows byte offsets, hex values, and ASCII representation (with . for non-printable characters).

Serve content from files

# Serve JSON response from file
./target/release/statico \
  --body @response.json \
  --header "Content-Type: application/json"

# Serve HTML page from file
./target/release/statico \
  --body @index.html \
  --header "Content-Type: text/html"

# Serve any file content
./target/release/statico \
  --body @data.xml \
  --header "Content-Type: application/xml"

Real-time metrics monitoring

The --meter flag enables real-time performance monitoring, displaying metrics every second:

# Enable metrics monitoring
./target/release/statico --meter

# Example output:
# req/s: 125430, req: 0.942 Gbps, res/s: 125430, res: 1.254 Gbps

The metrics show:

  • req/s: Requests received per second
  • req: Incoming bandwidth in Gbps
  • res/s: Responses sent per second
  • res: Outgoing bandwidth in Gbps

When you stop the server (Ctrl+C), it displays a final summary:

Total requests:  1234567
Total req bytes: 1234567890 (1.235 GB)
Total responses: 1234567
Total res bytes: 9876543210 (9.877 GB)

Architecture

Threading Model

  • The main thread parses command line arguments and spawns worker threads
  • Each worker thread creates its own socket bound to the same port using SO_REUSEPORT
  • Each worker thread runs a single-threaded Tokio runtime (current_thread)
  • The kernel load-balances incoming connections across threads

Socket Reuse

  • Linux/Android: Uses SO_REUSEPORT for true load balancing across threads
  • Other Unix systems: Falls back to SO_REUSEADDR (connections handled by one thread)
  • Windows: Uses SO_REUSEADDR

Runtime Support

Statico supports multiple async runtimes, selectable via the --runtime option:

Standard Runtimes

  • tokio (default): Multiple Single-threaded Tokio runtimes
  • tokio-local: Multiple Single-threaded Tokio runtimes spawning tasks on LocalSet.

io_uring-based Runtimes (Linux only)

These runtimes provide experimental support for Linux's io_uring interface and may offer significantly better performance:

  • tokio-uring: Requires compilation with --features tokio_uring

    • Run with --runtime tokio-uring
    • This is the runtime used in the benchmark results shown above
  • monoio: Requires compilation with --features monoio

    • Run with --runtime monoio
    • May show even better performance than tokio-uring
  • glommio: Requires compilation with --features glommio

    • Run with --runtime glommio
    • May show even better performance than tokio-uring

io_uring Configuration

When using io_uring-based runtimes, you can configure:

  • Submission queue size with --uring-entries (default: 4096)
  • Kernel-side polling with --uring-sqpoll <timeout_ms>

Note: io_uring runtimes currently provide a simplified implementation (only HTTP/1.1 is supported)

Use Cases

  • Load testing: Generate consistent HTTP responses for testing client applications
  • Mocking services: Simulate API endpoints with specific status codes and responses
  • Static file serving: Serve static content from files without a full web server
  • Health checks: Provide simple health check endpoints
  • Benchmarking: Test HTTP client performance with minimal server overhead
  • Development: Quick HTTP server for development and testing scenarios

Testing

Test the server with curl:

# Basic test
curl http://localhost:8080

# With verbose output to see headers
curl -v http://localhost:8080

# Load test with Apache Bench
ab -n 10000 -c 100 http://localhost:8080/

License

This project is provided as-is for educational and practical use.