Detailed documentation for the Modern C++ HTTP Server implementation.
- Installation & Quick Start
- API Documentation
- WebSocket Support
- Rate Limiting
- Configuration Reference
- Testing Guide
- Performance & Benchmarking
- Development Guide
- Deployment
# Clone the repository
git clone https://github.com/l1-ca0/http-server-cpp.git
cd http-server-cpp
# Build with different options
./scripts/build.sh release --clean --tests # Production build with tests
./scripts/build.sh debug --tests # Debug build with tests
./scripts/build.sh relwithdebinfo --install # Optimized with debug info| Option/Type | Description |
|---|---|
| debug | Debug build with sanitizers |
| release | Optimized production build |
| relwithdebinfo | Optimized with debug info |
| --clean | Clean build directory |
| --tests | Build and run unit tests |
| --install | Install to system |
| --verbose | Verbose output |
# Start server with default configuration
./build/http_server
# Start with custom configuration
./build/http_server config/server_config.json
# Start with custom options
./scripts/run.sh --port 3000 --host localhost#include "server.hpp"
using namespace http_server;
int main() {
// Create server with default config
ServerConfig config;
config.port = 8080;
// Enable HTTPS support
config.enable_https = true;
config.https_port = 8443;
config.ssl_certificate_path = "certs/server.crt";
config.ssl_private_key_path = "certs/server.key";
HttpServer server(config);
// Add a simple route
server.add_get_route("/hello", [](const HttpRequest& req) {
return HttpResponse::ok("Hello, World!");
});
// Add JSON API endpoint
server.add_get_route("/api/users", [](const HttpRequest& req) {
nlohmann::json users = {
{"users", {
{{"id", 1}, {"name", "Alice"}},
{{"id", 2}, {"name", "Bob"}}
}}
};
return HttpResponse::json_response(users.dump());
});
// Start server (handles both HTTP and HTTPS)
server.start(); // Blocks until shutdown
return 0;
}// GET routes
server.add_get_route("/users", handler);
server.add_get_route("/users/*", handler); // Wildcard support
// POST routes
server.add_post_route("/users", handler);
// PUT routes
server.add_put_route("/users/*", handler);
// DELETE routes
server.add_delete_route("/users/*", handler);auto handler = [](const HttpRequest& request) -> HttpResponse {
// Access request data
std::string path = request.path();
HttpMethod method = request.method();
std::string body = request.body();
// Headers (case-insensitive)
auto auth = request.get_header("Authorization");
if (!auth) {
return HttpResponse::bad_request("Missing authorization header");
}
// Query parameters
auto user_id = request.get_query_param("user_id");
// Content helpers
size_t length = request.content_length();
std::string type = request.content_type();
bool keep_alive = request.is_keep_alive();
// Build response
return HttpResponse::ok("Success")
.set_header("Content-Type", "application/json")
.set_cors_headers();
};// Simple responses
return HttpResponse::ok("Hello");
return HttpResponse::not_found();
return HttpResponse::bad_request("Invalid data");
return HttpResponse::internal_error();
// JSON responses
return HttpResponse::json_response(json_data);
// File responses
return HttpResponse::file_response("./public/index.html");
// Conditional file responses with ETag support
return HttpResponse::conditional_file_response("./public/data.json", request);
// Custom responses
return HttpResponse(HttpStatus::CREATED)
.set_json(data)
.set_header("Location", "/users/123")
.set_etag("abc123") // Add ETag for caching
.set_cors_headers()
.set_cache_control("public, max-age=3600");// CORS middleware
server.add_middleware([](const HttpRequest& req, HttpResponse& res) {
res.set_cors_headers();
return true; // Continue processing
});
// Authentication middleware
server.add_middleware([](const HttpRequest& req, HttpResponse& res) {
if (!req.has_header("Authorization")) {
res.set_status(HttpStatus::BAD_REQUEST)
.set_text("Missing authorization");
return false; // Stop processing
}
return true;
});
// Logging middleware
server.add_middleware([](const HttpRequest& req, HttpResponse& res) {
std::cout << req.method_to_string(req.method())
<< " " << req.path() << std::endl;
return true;
});The server supports HTTP ETag conditional requests for efficient caching:
// Automatic ETag generation and conditional responses
auto handler = [](const HttpRequest& request) -> HttpResponse {
// Use conditional file response for automatic ETag handling
return HttpResponse::conditional_file_response("./data/file.json", request);
// Returns:
// - 200 OK with content and ETag if file is new/modified
// - 304 Not Modified if client ETag matches (saves bandwidth)
};
// Manual ETag management
HttpResponse response = HttpResponse::ok(content);
response.set_etag("unique-content-hash"); // Strong ETag
response.set_etag("version-123", true); // Weak ETag (W/"version-123")
// Check if request has conditional headers
if (request.is_conditional_request()) {
auto if_none_match = request.get_if_none_match();
if (if_none_match && HttpResponse::etag_matches(etag, *if_none_match)) {
return HttpResponse(HttpStatus::NOT_MODIFIED)
.set_etag(etag)
.set_body(""); // Empty body for 304 responses
}
}
// ETag generation utilities
std::string content_etag = HttpResponse::generate_etag(content);
std::string file_etag = HttpResponse::generate_file_etag("./path/to/file");The server automatically compresses responses when:
- Client sends
Accept-Encoding: gzipheader - Response body is larger than 1024 bytes (configurable)
- Content type is compressible (text/*, application/json, etc.)
// Manual compression (if needed)
HttpResponse response;
response.set_compressed_body("Large response data", "gzip");
// Check if response is compressed
if (response.is_compressed()) {
// Handle compressed response
}Real-time bidirectional communication with full RFC 6455 compliance.
#include "server.hpp"
#include "websocket.hpp"
int main() {
HttpServer server(8080);
server.add_websocket_route("/ws", [](std::shared_ptr<WebSocketConnection> conn) {
conn->on_message([conn](const std::string& msg) {
conn->send_text("Echo: " + msg);
});
});
server.start();
return 0;
}const ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = () => ws.send('Hello!');
ws.onmessage = e => console.log('Received:', e.data);The HTTP server includes comprehensive rate limiting capabilities to protect against abuse and ensure fair resource usage. Multiple algorithms are supported with flexible key extraction strategies.
Allows burst traffic up to a configured capacity, then refills tokens at a specified rate.
#include "rate_limiter.hpp"
// Configure token bucket rate limiting
RateLimitConfig config;
config.strategy = RateLimitStrategy::TOKEN_BUCKET;
config.max_requests = 100; // Requests per window
config.burst_capacity = 20; // Burst capacity
config.window_duration = std::chrono::seconds(60);
RateLimiter limiter(config);Simple request counting per time window with hard resets.
// Configure fixed window rate limiting
RateLimitConfig config;
config.strategy = RateLimitStrategy::FIXED_WINDOW;
config.max_requests = 1000; // Requests per window
config.window_duration = std::chrono::seconds(3600); // 1 hour
RateLimiter limiter(config);More accurate limiting using timestamp-based request tracking.
// Configure sliding window rate limiting
RateLimitConfig config;
config.strategy = RateLimitStrategy::SLIDING_WINDOW;
config.max_requests = 500; // Requests per window
config.window_duration = std::chrono::seconds(300); // 5 minutes
RateLimiter limiter(config);Rate limiting can be applied based on different criteria:
// IP-based limiting (default)
auto ip_limiter = RateLimiter(config);
// User ID-based limiting
config.key_extractor = [](const HttpRequest& request) {
auto user_id = request.get_header("User-ID");
return user_id ? *user_id : "anonymous";
};
// API key-based limiting
config.key_extractor = RateLimitKeyExtractors::api_key;
// Endpoint path-based limiting
config.key_extractor = RateLimitKeyExtractors::endpoint_path;
// Combined IP and User-Agent
config.key_extractor = RateLimitKeyExtractors::ip_and_user_agent;Rate limiting integrates seamlessly with the HTTP server middleware pipeline:
#include "server.hpp"
#include "rate_limiter.hpp"
HttpServer server;
// Configure rate limiting
RateLimitConfig rate_config;
rate_config.strategy = RateLimitStrategy::TOKEN_BUCKET;
rate_config.max_requests = 100;
rate_config.burst_capacity = 10;
rate_config.window_duration = std::chrono::seconds(60);
RateLimiter limiter(rate_config);
// Add rate limiting middleware
server.add_middleware(limiter.create_middleware());
// Add routes
server.add_get_route("/api/data", [](const HttpRequest& req) {
return HttpResponse::ok().set_json(R"({"data": "value"})");
});Rate limiting can be enabled directly in server configuration:
ServerConfig server_config;
server_config.enable_rate_limiting = true;
server_config.rate_limit_config = rate_config;
HttpServer server(server_config);{
"rate_limiting": {
"enabled": true,
"strategy": "token_bucket",
"max_requests": 1000,
"burst_capacity": 50,
"window_duration_seconds": 3600,
"key_strategy": "ip_address"
}
}// Update configuration at runtime
RateLimitConfig new_config;
new_config.strategy = RateLimitStrategy::SLIDING_WINDOW;
new_config.max_requests = 2000;
new_config.window_duration = std::chrono::seconds(1800);
limiter.update_config(new_config);When rate limiting is active, responses include informative headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 3600
X-RateLimit-Type: token_bucket
Customize the response when rate limits are exceeded:
config.rate_limit_response = []() {
return HttpResponse(HttpStatus::TOO_MANY_REQUESTS)
.set_json(R"({
"error": "Rate limit exceeded",
"retry_after": 60,
"documentation": "https://api.example.com/docs/rate-limits"
})");
};Access rate limiting statistics for monitoring:
auto stats = limiter.get_statistics();
std::cout << "Total requests: " << stats.total_requests << std::endl;
std::cout << "Blocked requests: " << stats.blocked_requests << std::endl;
std::cout << "Block rate: " << stats.get_block_rate() << std::endl;
std::cout << "Active keys: " << stats.active_keys << std::endl;-
Choose the Right Algorithm:
- Token Bucket: For APIs that need to handle burst traffic
- Fixed Window: For simple, predictable rate limiting
- Sliding Window: For more accurate and fair limiting
-
Key Selection:
- IP-based: General protection against abuse
- User-based: Fair usage policies for authenticated users
- API key-based: Service tier differentiation
- Endpoint-based: Protect expensive operations
-
Configuration:
- Set reasonable burst capacities for token bucket
- Choose appropriate window durations
- Monitor and adjust based on usage patterns
-
Error Handling:
- Provide clear error messages
- Include retry-after information
- Log rate limiting events for analysis
## Configuration Reference
### JSON Configuration File
```json
{
"host": "0.0.0.0",
"port": 8080,
"enable_https": true,
"https_port": 8443,
"ssl_certificate_path": "certs/server.crt",
"ssl_private_key_path": "certs/server.key",
"ssl_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"ssl_verify_client": false,
"thread_pool_size": 4,
"document_root": "./public",
"max_connections": 1000,
"keep_alive_timeout": 30,
"max_request_size": 1048576,
"enable_logging": true,
"log_file": "server.log",
"serve_static_files": true,
"index_files": [
"index.html",
"index.htm",
"default.html"
],
"enable_compression": true,
"compression_min_size": 1024,
"websocket": {
"enabled": true,
"ping_interval": 30,
"connection_timeout": 60,
"max_frame_size": 1048576,
"max_connections": 100
},
"rate_limiting": {
"enabled": true,
"strategy": "token_bucket",
"max_requests": 1000,
"burst_capacity": 50,
"window_duration_seconds": 3600,
"key_strategy": "ip_address"
},
"compression_level": 6,
"compressible_types": [
"text/plain",
"text/html",
"text/css",
"application/javascript",
"application/json",
"application/xml",
"text/xml"
],
"mime_types": {
"html": "text/html; charset=utf-8",
"css": "text/css",
"js": "application/javascript",
"json": "application/json",
"png": "image/png",
"jpg": "image/jpeg"
}
}
ServerConfig config;
config.host = "127.0.0.1";
config.port = 8080;
// HTTPS Configuration
config.enable_https = true;
config.https_port = 8443;
config.ssl_certificate_path = "certs/server.crt";
config.ssl_private_key_path = "certs/server.key";
config.ssl_ciphers = "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256";
config.ssl_verify_client = false;
config.thread_pool_size = 4;
config.document_root = "./public";
config.serve_static_files = true;
config.keep_alive_timeout = std::chrono::seconds(30);
config.enable_compression = true;
config.compression_min_size = 1024;
config.compression_level = 6;
HttpServer server(config);| Option | Type | Default | Description |
|---|---|---|---|
| host | string | "0.0.0.0" | Server bind address |
| port | int | 8080 | HTTP server port |
| enable_https | bool | false | Enable HTTPS/SSL support |
| https_port | int | 8443 | HTTPS server port |
| ssl_certificate_path | string | "" | Path to SSL certificate file (.crt or .pem) |
| ssl_private_key_path | string | "" | Path to SSL private key file (.key or .pem) |
| ssl_ciphers | string | Modern cipher suite | SSL/TLS cipher configuration |
| ssl_verify_client | bool | false | Require client certificate verification |
| thread_pool_size | int | 4 | Thread pool size (currently unused) |
| document_root | string | "./public" | Static files directory |
| max_connections | int | 1000 | Maximum concurrent connections |
| keep_alive_timeout | int | 30 | Keep-alive timeout in seconds |
| max_request_size | int | 1048576 | Maximum request size in bytes |
| enable_logging | bool | true | Enable request logging |
| log_file | string | "server.log" | Log file path |
| serve_static_files | bool | true | Enable static file serving |
| enable_compression | bool | true | Enable gzip compression |
| compression_min_size | int | 1024 | Minimum size for compression |
| compression_level | int | 6 | Compression level (1-9) |
| websocket.enabled | bool | true | Enable WebSocket support |
| websocket.ping_interval | int | 30 | WebSocket ping interval in seconds |
| websocket.connection_timeout | int | 60 | WebSocket connection timeout in seconds |
| websocket.max_frame_size | int | 1048576 | Maximum WebSocket frame size in bytes |
| websocket.max_connections | int | 100 | Maximum concurrent WebSocket connections |
| rate_limiting.enabled | bool | false | Enable rate limiting |
| rate_limiting.strategy | string | "token_bucket" | Rate limiting algorithm ("token_bucket", "fixed_window", "sliding_window") |
| rate_limiting.max_requests | int | 1000 | Maximum requests per window |
| rate_limiting.burst_capacity | int | 50 | Burst capacity for token bucket algorithm |
| rate_limiting.window_duration_seconds | int | 3600 | Time window duration in seconds |
| rate_limiting.key_strategy | string | "ip_address" | Key extraction strategy ("ip_address", "user_id", "api_key", "endpoint_path") |
| Suite | Description | Coverage |
|---|---|---|
| HttpServerTest | Core functionality | Config loading, route/middleware registration, server stats |
| HttpRequestTest | Request parsing | HTTP methods, headers, query params, keep-alive, request validation |
| HttpResponseTest | Response generation | Status codes, headers, body, MIME types, compression, serialization |
| SecurityTest | Security validation | Path traversal, request size, header injection, security headers |
| PerformanceTest | Performance testing | Concurrency, memory usage, stress, rapid config, malformed requests |
| HttpProtocolTest | HTTP/1.1 compliance | Chunked encoding, compression, protocol edge cases |
| HttpsServerTest | HTTPS/SSL testing | SSL context, certificates, encrypted connections, HTTPS configuration |
| WebSocketTest | WebSocket functionality | RFC 6455 compliance, frame handling, handshakes, connection management |
| RateLimiterTest | Rate limiting functionality | Multiple algorithms, key extraction, middleware integration, abuse protection |
| ETagTest | ETag cache validation | ETag generation, conditional requests, HTTP 304 responses, cache headers |
# Build and run all tests
./scripts/build.sh debug --tests
# Run specific test suite
./build/test_runner --gtest_filter="HttpServerTest.*"
# Run HTTPS tests
./build/test_runner --gtest_filter="HttpsServerTest.*"
# Run WebSocket tests
./build/test_runner --gtest_filter="WebSocketTest.*"
# Run SSL-specific tests
./build/test_runner --gtest_filter="*Ssl*"
# Run WebSocket-specific tests
./build/test_runner --gtest_filter="*WebSocket*"
# Run with detailed output
./build/test_runner --gtest_print_time=1
# Run tests with verbose output
./build/test_runner --gtest_filter="*" --gtest_print_time=1
# Memory leak detection (if built with sanitizers)
./scripts/build.sh debug --tests
# Tests automatically run with AddressSanitizer and UBSan# Start server
./build/http_server
# Test basic functionality
curl http://localhost:8080/
curl http://localhost:8080/api/status
# Test HTTPS functionality
curl -k https://localhost:8443/
curl -k https://localhost:8443/api/status
# Test with headers
curl -H "Accept-Encoding: gzip" http://localhost:8080/
curl -k -H "Accept-Encoding: gzip" https://localhost:8443/
# Test POST requests
curl -X POST -d "test data" http://localhost:8080/api/echo
# Test static files
curl http://localhost:8080/index.html# Start server with WebSocket support
./build/http_server
# Test WebSocket connection using wscat (install: npm install -g wscat)
wscat -c ws://localhost:8080/ws
# Test WebSocket over HTTPS
wscat -c wss://localhost:8443/ws --no-check
# Test WebSocket with specific subprotocol
wscat -c ws://localhost:8080/ws -s echo-protocol
# WebSocket load testing (install: npm install -g ws-load-test)
ws-load-test -c 100 -m 1000 ws://localhost:8080/ws
# Manual WebSocket testing with curl (HTTP upgrade)
curl -i -N -H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
http://localhost:8080/ws<!DOCTYPE html>
<html>
<head><title>WebSocket Test</title></head>
<body>
<script>
const ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = function() {
console.log('Connected to WebSocket');
ws.send('Hello from browser!');
};
ws.onmessage = function(event) {
console.log('Received:', event.data);
};
ws.onclose = function(event) {
console.log('Connection closed:', event.code, event.reason);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
// Send periodic messages
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(`Message at ${new Date().toISOString()}`);
}
}, 5000);
</script>
</body>
</html># Built-in benchmark script with wrk
./scripts/benchmark.sh --tool wrk --duration 30s --connections 100
# Apache Bench
./scripts/benchmark.sh --tool ab --requests 10000 --concurrency 100
# Custom scenarios
./scripts/benchmark.sh --custom --scenario static_files
./scripts/benchmark.sh --custom --scenario json_api# Using wrk
wrk -t12 -c400 -d30s http://localhost:8080/
# Using Apache Bench
ab -n 10000 -c 100 http://localhost:8080/
# Using curl for simple tests
for i in {1..1000}; do curl -s http://localhost:8080/ > /dev/null; done# Server statistics endpoint
curl http://localhost:8080/api/status
# Monitor with htop/top
htop -p $(pgrep http_server)
# Memory usage
ps -o pid,vsz,rss,comm -p $(pgrep http_server)cpp-http-server/
├── include/ # Public header files
│ ├── server.hpp
│ ├── connection.hpp
│ ├── ssl_connection.hpp
│ ├── request.hpp
│ ├── response.hpp
│ ├── thread_pool.hpp
│ └── compression.hpp
├── src/ # Source implementations
│ ├── main.cpp
│ ├── server.cpp
│ ├── connection.cpp
│ ├── ssl_connection.cpp
│ ├── request.cpp
│ ├── response.cpp
│ ├── thread_pool.cpp
│ └── compression.cpp
├── test/ # Unit and protocol tests
│ ├── test_server.cpp
│ ├── test_request.cpp
│ ├── test_response.cpp
│ ├── test_security.cpp
│ ├── test_performance.cpp
│ ├── test_http_protocol.cpp
│ └── test_https.cpp
├── config/ # JSON configuration files
├── public/ # Static files for serving
├── certs/ # SSL certificates for HTTPS
├── scripts/ # Build, run, and benchmark scripts
├── docker/ # Docker and container configs
└── CMakeLists.txt # CMake build configuration
- Boost.Asio - Asynchronous I/O operations
- OpenSSL - SSL/TLS support for HTTPS functionality
- nlohmann/json - JSON configuration and responses
- Google Test - Unit testing framework
- ZLIB - Gzip compression support
All dependencies are automatically fetched and configured by CMake.
The project uses modern CMake (3.20+) with:
- C++20 standard requirement
- Automatic dependency management with FetchContent
- Platform-specific optimizations
- Debug builds with sanitizers
- Comprehensive compiler warnings
- Modern C++20 features (concepts, ranges, coroutines where applicable)
- RAII and smart pointers
- Const-correctness
- Exception safety
- Clear naming conventions
For HTTPS deployment, you'll need SSL certificates:
# Generate self-signed certificates for development/testing
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes
# Or use Let's Encrypt for production
certbot certonly --standalone -d your-domain.com
# Configure paths in server config
{
"enable_https": true,
"https_port": 8443,
"ssl_certificate_path": "/path/to/server.crt",
"ssl_private_key_path": "/path/to/server.key",
"ssl_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
}# Build Docker image
docker build -t cpp-http-server .
# Run with Docker
docker run -p 8080:8080 cpp-http-server
# Use Docker Compose
docker-compose up
# Production deployment with Nginx reverse proxy
docker-compose -f docker-compose.prod.yml up[Unit]
Description=C++ HTTP Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/cpp-http-server
ExecStart=/opt/cpp-http-server/http_server config/server_config.json
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetExample Nginx configuration for HTTP and HTTPS:
# HTTP server (redirect to HTTPS)
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server (proxy to backend)
server {
listen 443 ssl http2;
server_name example.com;
# SSL configuration (if terminating SSL at proxy)
ssl_certificate /path/to/ssl/cert.pem;
ssl_certificate_key /path/to/ssl/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
location / {
# Proxy to HTTP backend (SSL termination at proxy)
proxy_pass http://127.0.0.1:8080;
# Or proxy to HTTPS backend (end-to-end encryption)
# proxy_pass https://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}The server includes built-in statistics:
# Get server status
curl http://localhost:8080/api/statusFor production monitoring, consider:
- Prometheus metrics collection
- Log aggregation with ELK stack
- Health check endpoints
- Resource monitoring with tools like htop, iostat
- Configure HTTPS with proper SSL certificates for production
- Run behind reverse proxy for additional security layers
- Configure firewall rules
- Set appropriate file permissions for SSL certificates
- Use non-root user for service
- Implement rate limiting at proxy level
- Regular security updates
- Use strong cipher suites and disable weak protocols
This server has limitations compared to production servers:
- HTTP/1.1 Only - No HTTP/2 or HTTP/3 support
- No Range Requests - Partial content delivery not implemented
- No Built-in Authentication - Applications must implement custom auth schemes (server supports Authorization headers)
- No HTTP/2 over TLS - HTTPS uses HTTP/1.1 only
- No Advanced SSL Features - Missing HSTS, certificate pinning, OCSP stapling
- Self-Signed Certificates - Production deployments need proper CA-signed certificates
For production use, consider deploying behind a full-featured reverse proxy like Nginx or Apache for advanced features.