Skip to the content.

< Previous: Basic Usage | 🏠 Home | Next: Performance & Tuning >


Advanced Usage

This section explores advanced HTTP request processing, handling JSON payloads, multi-threaded safety, and defragmentation concepts inside cpp-http.

JSON Payloads

Because cpp-http stores raw body strings in HttpRequest::body, you can easily integrate any JSON library (like nlohmann/json) inside your route handlers:

#include "cpphttp.hpp"
#include <nlohmann/json.hpp>
#include <iostream>

using json = nlohmann::json;

int main() {
    cpphttp::HttpServer server(8080);

    server.Post("/api/users", [](const cpphttp::HttpRequest& request) {
        cpphttp::HttpResponse response;
        response.headers["Content-Type"] = "application/json";

        try {
            // Parse raw request body into JSON
            auto req_json = json::parse(request.body);
            std::string username = req_json.at("username");

            // Build success JSON response
            json res_json;
            res_json["status"] = "success";
            res_json["message"] = "User " + username + " registered successfully!";
            
            response.status_code = 200;
            response.body = res_json.dump();
        } catch (const std::exception& e) {
            // Build error JSON response
            json err_json;
            err_json["status"] = "error";
            err_json["error_message"] = e.what();

            response.status_code = 400;
            response.status_message = "Bad Request";
            response.body = err_json.dump();
        }

        return response;
    });

    server.Start();
    // Wait...
}

Dynamic Routing & Path Parameters

cpp-http supports dynamic routing segments using the :param syntax and wildcard segments using the * suffix:

// 1. Path parameters matching (e.g. GET /users/123/profile)
server.Get("/users/:id/profile", [](const cpphttp::HttpRequest& request) {
    std::string id = request.path_params.at("id"); // captures "123"
    return cpphttp::HttpResponse::Plain("User profile: " + id);
});

// 2. Wildcard matching (e.g. GET /assets/js/jquery.js)
server.Get("/assets/*", [](const cpphttp::HttpRequest& request) {
    std::string filename = request.path_params.at("*"); // captures "js/jquery.js"
    return cpphttp::HttpResponse::Plain("File requested: " + filename);
});

Query Parameters & URL Decoding

Query parameters (e.g., ?q=hello&sort=desc) are automatically stripped from routes before path matching to ensure request dispatching runs correctly. They are decoded and stored inside HttpRequest::query_params:

server.Get("/search", [](const cpphttp::HttpRequest& request) {
    std::string search_query = request.query_params.count("q") ? request.query_params.at("q") : "";
    std::string sort = request.query_params.count("sort") ? request.query_params.at("sort") : "asc";

    return cpphttp::HttpResponse::Plain("Query: " + search_query + " (sorted " + sort + ")");
});

All route parameters, path names, and query values are percent-decoded automatically (e.g., %20 becomes a space character ` `).

Middleware Pipeline

You can register middleware handlers that run sequentially before any request matches a route. If any middleware returns false, the request chain is aborted, and the middleware’s response is returned immediately (useful for authentication or validation).

cpp-http supports both global and path-scoped middleware:

1. Global Middleware

// Register global logging middleware (applies to all routes)
server.Use([](cpphttp::HttpRequest& request, cpphttp::HttpResponse& response) {
    std::cout << "[Middleware Log] Request on: " << request.path << "\n";
    return true;
});

2. Path-Scoped Middleware

// Register scoped auth middleware (only runs on paths starting with "/api")
server.Use("/api", [](cpphttp::HttpRequest& request, cpphttp::HttpResponse& response) {
    if (request.GetHeader("Authorization") != "secret-token") {
        response = cpphttp::HttpResponse::Plain("Unauthorized", 401);
        return false; // abort request chain
    }
    return true; // continue request processing
});

3. Rate Limiting Middleware

cpp-http provides a built-in RateLimiter middleware class to limit requests per IP address over a sliding time window.

It extracts the client’s IP from the X-Forwarded-For HTTP header (falling back to the TCP listener’s peer address). If an IP exceeds the configured request limit in the given time window, the rate limiter aborts the middleware chain, returns a 429 Too Many Requests status, and sets a Retry-After header indicating when the client can try again.

Global and Scoped Rate Limiting

Since RateLimiter is implemented as standard middleware, you can register it globally to protect the entire server, or scoped to apply stricter limit configurations for specific endpoints or path prefixes:

#include "cpphttp.hpp"

int main() {
    cpphttp::HttpServer server(8080);

    // 1. Global rate limiting (applies to all routes)
    // Limits each client IP to at most 100 requests every 60 seconds
    server.Use(cpphttp::RateLimiter(100, std::chrono::seconds(60)));

    // 2. Scoped rate limiting (applies only to paths starting with "/api/login")
    // Stricter limits for login endpoints: max 5 attempts per minute
    server.Use("/api/login", cpphttp::RateLimiter(5, std::chrono::seconds(60)));

    server.Get("/api/resource", [](const cpphttp::HttpRequest& req) {
        return cpphttp::HttpResponse::Plain("Success!");
    });

    server.Start();
}

4. CORS Middleware

cpp-http provides a built-in configurable Cors middleware class to handle Cross-Origin Resource Sharing (CORS).

It automatically intercepts preflight OPTIONS requests and returns a 204 No Content response with the appropriate Access-Control-Allow-* headers. For actual requests, it injects the necessary CORS headers into the final response.

#include "cpphttp.hpp"

int main() {
    cpphttp::HttpServer server(8080);

    // Configure CORS settings
    cpphttp::CorsConfig cors_config;
    cors_config.allow_origin = "https://example.com";
    cors_config.allow_credentials = true;
    cors_config.expose_headers = {"X-Custom-Header"};
    
    // 1. Global CORS (applies to all routes)
    // server.Use(cpphttp::Cors(cors_config));

    // 2. Scoped CORS (applies only to paths starting with "/api")
    server.Use("/api", cpphttp::Cors(cors_config));

    server.Get("/api/data", [](const cpphttp::HttpRequest& req) {
        return cpphttp::HttpResponse::Plain("Data with CORS");
    });

    server.Start();
}

TCP Defragmentation & Pipelining

In TCP connections, data packets can arrive fragmented (broken into multiple chunks) or coalesced (multiple requests sent sequentially in one TCP stream, known as HTTP pipelining). cpp-http handles both scenarios automatically:

  1. Fragmentation Protection: The server buffers incoming bytes inside an internal session buffer. It searches for the header terminator (\r\n\r\n). If not found, it waits for more TCP packets. Once the header is found, it extracts Content-Length. If the buffered data is smaller than the body size, it defers routing and waits for more packet events.

  2. Pipelining Support: After a route callback is executed and the response is sent, the server clears only the processed request block from the session buffer, leaving subsequent HTTP requests untouched in the queue:

    buffer.erase(buffer.begin(), buffer.begin() + body_start_idx + content_length);
    

Routing and 404 Handler

When a client sends a request, the server maps the HTTP method and path into a routing key: Method:Path (e.g., GET:/api/users).

If no route exists in the routing map matching the incoming key, cpp-http automatically generates and returns a standard plain-text 404 response:

HttpResponse not_found;
not_found.status_code = 404;
not_found.status_message = "Not Found";
not_found.headers["Content-Type"] = "text/plain";
not_found.body = "404 - Route not found.";

Multi-Threaded Safety

HttpServer dispatches incoming data to a worker pool provided by cpp-tcpnet. The worker pool uses session-affinity hashing — all packets for a given TCP connection are always routed to the same worker thread. This guarantees in-order delivery per connection while still processing different connections in parallel across all available CPU cores.

Route callbacks may execute concurrently across connections, so:

#include <mutex>

std::mutex db_mutex;
int user_count = 0;

server.Post("/register", [](const cpphttp::HttpRequest& req) {
    std::lock_guard<std::mutex> lock(db_mutex);
    user_count++;
    // safely perform modifications...
});

HTTP Streaming

cpp-http supports streaming for both large uploads (server-side) and large downloads (client-side), allowing data to be processed incrementally without buffering the entire payload in memory.

Server-Side: AddStreamRoute

Register a streaming upload route using AddStreamRoute. The handler is invoked incrementally for each received chunk. When is_final is true, the complete body has been received and you should return your final HttpResponse:

#include "cpphttp.hpp"
#include <iostream>

int main() {
    cpphttp::HttpServer server(8080);

    // Accumulate the total bytes received across chunks
    server.AddStreamRoute("POST", "/upload",
        [](const cpphttp::HttpRequest& req,
           const std::string& chunk,
           bool is_final) -> std::optional<cpphttp::HttpResponse> {

            std::cout << "Received chunk of " << chunk.size() << " bytes\n";

            if (is_final) {
                // Return the response only when all data has arrived
                return cpphttp::HttpResponse::Plain("Upload complete!");
            }
            return std::nullopt; // Signal: keep streaming
        });

    server.Start();
    // ...
}

Key points:

Client-Side: Streaming Downloads (GetStream / PostStream)

Use GetStream or PostStream to receive large responses incrementally instead of buffering the full body:

cpphttp::HttpClient client("127.0.0.1", 8080);

// Stream a large file download chunk-by-chunk
cpphttp::HttpResponse res = client.GetStream(
    "/large-file",
    [](const cpphttp::HttpResponse& partial_res,
       const std::string& chunk,
       bool is_final) {
        std::cout << "Got " << chunk.size() << " bytes";
        if (is_final) std::cout << " (final)";
        std::cout << "\n";
    });

std::cout << "Status: " << res.status_code << "\n";

Client-Side: Streaming Uploads (PostStreamAsync / PutStreamAsync)

For large uploads, use the async stream upload API. Provide a stream_provider callback that returns the next chunk of data to send, and a content_length for the Content-Length header (or use chunked encoding via headers):

cpphttp::HttpClient client("127.0.0.1", 8080);

// Stream a 10MB upload in 1MB chunks
std::string large_data(10 * 1024 * 1024, 'X');
size_t offset = 0;
const size_t chunk_size = 1024 * 1024;

auto future = client.PostStreamAsync(
    "/upload",
    {/* headers */},
    [&](size_t /*hint*/) -> std::string {
        if (offset >= large_data.size()) return ""; // Signal EOF
        size_t take = std::min(chunk_size, large_data.size() - offset);
        std::string chunk = large_data.substr(offset, take);
        offset += take;
        return chunk;
    },
    large_data.size() // Content-Length
);

cpphttp::HttpResponse res = future.get();
std::cout << "Upload status: " << res.status_code << "\n";

The PutStreamAsync API is identical but sends an HTTP PUT request.

Multipart Form-Data Parsing

cpp-http provides a ParseMultipart utility to handle file uploads and form-data. It splits the request body based on the boundary token extracted from the Content-Type header:

#include "cpphttp.hpp"

server.Post("/upload", [](const cpphttp::HttpRequest& request) {
    // 1. Get multipart boundary from Content-Type header
    std::string ct = request.GetHeader("Content-Type");
    size_t boundary_pos = ct.find("boundary=");
    if (boundary_pos == std::string::npos) {
        return cpphttp::HttpResponse::Plain("Bad Request", 400);
    }
    std::string boundary = ct.substr(boundary_pos + 9);

    // 2. Parse request body
    std::vector<cpphttp::MultipartPart> parts = cpphttp::ParseMultipart(request.body, boundary);
    
    for (const auto& part : parts) {
        std::cout << "Part Name: " << part.name << "\n";
        if (!part.filename.empty()) {
            std::cout << "Uploaded File: " << part.filename << " (" << part.content_type << ")\n";
            // part.data contains raw file content
        }
    }

    return cpphttp::HttpResponse::Plain("Upload completed!");
});

Serving Static Files

cpp-http provides a convenient built-in mechanism to serve an entire directory of static files using HttpServer::StaticDir:

server.StaticDir(const std::string &route_prefix, const std::string &directory_path, bool spa_mode = false);

Example Usage

#include "cpphttp.hpp"
#include <iostream>

int main() {
    cpphttp::HttpServer server(8080);

    // Serve files in the "public" local directory under "/static"
    // For example:
    // - Local file: "public/index.html" -> URL: "http://localhost:8080/static/index.html"
    // - Local file: "public/css/style.css" -> URL: "http://localhost:8080/static/css/style.css"
    server.StaticDir("/static", "./public");

    server.Start();
    // Wait...
}

Features & Security

Response Caching

To optimize server performance, cpp-http provides a ResponseCache utility to cache handler responses. It is implemented as a wrapper around route handlers, which intercepts request processing to serve cached responses on hits, and caches new responses on misses.

Key Characteristics:

Example Usage:

#include "cpphttp.hpp"

int main() {
    cpphttp::HttpServer server(8080);
    
    // Create a response cache with a default 10-second TTL
    cpphttp::ResponseCache cache(std::chrono::seconds(10));

    // Wrap a route handler to cache its response for the default duration
    server.Get("/time", cache.Wrap([](const cpphttp::HttpRequest& req) {
        auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        return cpphttp::HttpResponse::Plain("Current time: " + std::string(std::ctime(&now)));
    }));

    // Wrap with a custom cache duration (e.g. 5 seconds)
    server.Get("/fast-expire", cache.Wrap([](const cpphttp::HttpRequest& req) {
        return cpphttp::HttpResponse::Plain("This cache expires fast!");
    }, std::chrono::seconds(5)));

    server.Start();
}

Transparent HTTP Compression (Zstandard, Brotli, Gzip)

To optimize network bandwidth, cpp-http supports automatic transparent compression and decompression out-of-the-box:

Configuring Compression Settings and Filters

Developers can configure quality levels, size limits, and control which payloads are compressed:

1. Quality & Level Settings

Adjusting compression settings dynamically on the HttpServer instance. Setting out-of-bounds levels throws a std::invalid_argument exception:

Algorithm Method Default Valid Range Details
Zstandard SetZstdCompressionLevel(int level) 3 [ZSTD_minCLevel(), ZSTD_maxCLevel()] (typically [-5, 22]) High-speed compression with excellent ratios
Brotli SetBrotliQuality(int quality) 4 [0, 11] Highly optimized web compression (quality 4-6 is ideal for dynamic content)
Gzip SetGzipCompressionLevel(int level) -1 (Z_DEFAULT_COMPRESSION) [-1, 9] Standard compression compatibility

2. Size Threshold & MIME Filters

To optimize CPU performance and prevent redundant processing:

Example of adjusting compression settings:

cpphttp::HttpServer server(8080);

// Adjust quality levels
server.SetZstdCompressionLevel(5);
server.SetBrotliQuality(5);
server.SetGzipCompressionLevel(1); // 1 = Z_BEST_SPEED

// Adjust minimum size threshold to 512 bytes
server.SetMinCompressionSize(512);

server.Start();

HTTP Redirect Method Downgrade

When the client performs redirect following (e.g. following a 302 Found or 303 See Other response status), it automatically downgrades POST requests to GET requests for the redirected URL. This aligns with standard web-browser redirection behavior.

WebSocket Support

cpp-http provides comprehensive support for WebSockets, allowing you to build both WebSocket servers and clients using a clean, asynchronous event-driven API.

1. Server-Side WebSocket Routing

To handle WebSocket upgrades on the server, define a WebSocketBehavior struct specifying event callbacks, then register it using server.WebSocket(...):

#include "cpphttp.hpp"
#include <iostream>

int main() {
    cpphttp::HttpServer server(8080);

    cpphttp::WebSocketBehavior ws_behavior;
    
    // Callback when a client completes the handshake and opens the connection
    ws_behavior.on_open = [](std::shared_ptr<cpphttp::WebSocketConnection> conn) {
        std::cout << "Client connected: session_id=" << conn->GetSessionId() << "\n";
    };

    // Callback when a text message frame is received
    ws_behavior.on_message = [](std::shared_ptr<cpphttp::WebSocketConnection> conn, const std::string &msg) {
        std::cout << "Received text: " << msg << "\n";
        conn->Send("Echo: " + msg); // Echo the text message back to the client
    };

    // Callback when a binary message frame is received
    ws_behavior.on_binary = [](std::shared_ptr<cpphttp::WebSocketConnection> conn, const std::vector<uint8_t> &data) {
        std::cout << "Received binary frame of size: " << data.size() << " bytes\n";
        conn->SendBinary(data); // Echo binary data back
    };

    // Callback when connection closes
    ws_behavior.on_close = [](std::shared_ptr<cpphttp::WebSocketConnection> conn) {
        std::cout << "Client disconnected\n";
    };

    // Register WebSocket route
    server.WebSocket("/ws", std::move(ws_behavior));

    server.Start();
    // Wait...
}

The server-side connection object (WebSocketConnection) provides helper methods to interact with the socket:

2. Client-Side WebSocket (WebSocketClient)

The library includes a dedicated client-side WebSocketClient class to connect to any WebSocket server (supporting both ws:// and wss:// protocols):

#include "cpphttp.hpp"
#include <iostream>
#include <thread>

int main() {
    cpphttp::WebSocketClient client;

    // Register event callbacks
    client.OnOpen([]() {
        std::cout << "Connected to server!\n";
    });

    client.OnMessage([](const std::string &msg) {
        std::cout << "Received message: " << msg << "\n";
    });

    client.OnBinary([](const std::vector<uint8_t> &data) {
        std::cout << "Received binary data of size: " << data.size() << " bytes\n";
    });

    client.OnClose([](uint16_t status_code, const std::string &reason) {
        std::cout << "Closed connection: code=" << status_code << ", reason=" << reason << "\n";
    });

    // Connect asynchronously to WebSocket endpoint
    if (client.Connect("ws://127.0.0.1:8080/ws")) {
        client.Send("Hello from WebSocketClient!");
        
        // Sleep to allow background worker to process responses
        std::this_thread::sleep_for(std::chrono::seconds(2));
        
        client.Close();
    } else {
        std::cerr << "Failed to connect to server\n";
    }

    return 0;
}

The client-side WebSocketClient exposes:

HTTP/3 (HTTP-over-QUIC) & Hybrid Transports

cpp-http provides comprehensive support for HTTP/3 (HTTP-over-QUIC) when compiled with QUIC enabled (CPPHTTP_ENABLE_QUIC). This includes dedicated HTTP/3 servers, HTTP/3 clients, and a dual TCP/UDP hybrid server model.

1. Architecture & Transport Classes

The library abstracts the network protocol via transport templates. The core server and client classes are HttpServerImpl<Transport> and HttpClientImpl<Transport>.

Depending on your compilation options, the following aliases are available:

Class / Type Alias Underlying Transport Protocol / socket
HttpServer TcpServerTransport TCP (Standard HTTP / HTTPS)
HttpClient TcpClientTransport TCP (Standard HTTP / HTTPS client)
QuicHttpServer QuicServerTransport UDP (HTTP/3 Server)
HybridHttpServer HybridServerTransport TCP & UDP (Dual HTTPS & HTTP/3 Server)

2. Hybrid HTTP/3 Server

A HybridHttpServer hosts both a standard TCP (HTTPS) listener and a UDP (QUIC) listener on the same port. By default, the server automatically injects the Alt-Svc header into TCP responses to advertise HTTP/3 support to client browsers (e.g., Alt-Svc: h3=":4433").

Configuration Example

Below is a demonstration of setting up a hybrid server with SSL certificates:

#include "cpphttp.hpp"
#include <iostream>

int main() {
    uint16_t port = 4433;
    
    try {
        cpphttp::HybridHttpServer server(port);

        // 1. Configure SSL/TLS certificates for HTTPS on the TCP listener
        cpptcpnet::TcpListener::SslConfig ssl_config;
        ssl_config.cert_file = "certs/server.crt";
        ssl_config.key_file = "certs/server.key";

        try {
            server.GetTransport()->GetTcpTransport().GetListener().EnableSSL(ssl_config);
            std::cout << "HTTPS enabled on TCP port." << std::endl;
        } catch (const std::exception &e) {
            std::cerr << "Warning: TCP HTTPS failed to start: " << e.what() << std::endl;
        }

        // 2. Configure the same certificates for the UDP/QUIC listener
        try {
            server.GetTransport()->GetQuicTransport().GetServer().SetCertificate(
                "certs/server.crt", "certs/server.key"
            );
            std::cout << "Certificates loaded for QUIC server." << std::endl;
        } catch (const std::exception &e) {
            std::cerr << "Warning: QUIC cert configuration failed: " << e.what() << std::endl;
        }

        // 3. Register route handlers (shared by both TCP and QUIC listeners)
        server.Get("/hello", [](const cpphttp::HttpRequest &req) {
            return cpphttp::HttpResponse::Plain("Hello World over TCP or QUIC!");
        });

        // 4. (Optional) Control Alt-Svc headers
        // server.SetAdvertiseAltSvc(false); // Disable Alt-Svc header advertisement

        // 5. Start the server
        server.Start();
        std::cout << "Hybrid server running on port " << port << std::endl;
        std::cin.get();
        server.Stop();
    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

3. HTTP Client over QUIC

You can make HTTP queries over QUIC using the client template instantiated with QuicClientTransport:

#include "cpphttp.hpp"
#include <iostream>

int main() {
    try {
        // Instantiate the client with QuicClientTransport
        cpphttp::HttpClientImpl<cpphttp::QuicClientTransport> client("127.0.0.1", 4433);
        client.SetTimeout(std::chrono::seconds(5));

        // Send a GET request
        cpphttp::HttpResponse response = client.Get("/hello");

        std::cout << "Status: " << response.status_code << " " << response.status_message << "\n";
        std::cout << "Body: " << response.body << "\n";
    } catch (const std::exception &e) {
        std::cerr << "QUIC Client Exception: " << e.what() << std::endl;
    }
}

4. Low-Level HTTP/3 & QPACK Helpers

cpp-http exposes internal helpers for HTTP/3 framing and QPACK header compression:

Internal Library Logging

cpp-http provides a global logging API to capture internal diagnostics, warnings, and connection errors from HttpClient and WebSocketClient. By default, these logs are silent. You can register a custom logger callback to print or save these diagnostics.

Custom Logger Callback Registration

Set a custom logger using cpphttp::SetLogger():

#include "cpphttp.hpp"
#include <iostream>

int main() {
    // 1. Register a custom log handler callback
    cpphttp::SetLogger([](cpphttp::LogSeverity severity, const std::string &className, const std::string &message) {
        std::string level = "INFO";
        if (severity == cpphttp::LogSeverity::Warning) level = "WARNING";
        if (severity == cpphttp::LogSeverity::Error) level = "ERROR";

        std::cout << "[HTTP-LIB][" << level << "][" << className << "] " << message << std::endl;
    });

    // 2. To disable internal logging at any time, pass nullptr
    cpphttp::SetLogger(nullptr);
}

The severity of internal messages is represented by cpphttp::LogSeverity:


< Previous: Basic Usage | 🏠 Home | Next: Performance & Tuning >