Skip to content

# Advanced Networking

This guide covers advanced Glaze HTTP features including CORS support, WebSocket functionality, SSL/TLS encryption, and HTTP client capabilities.

> Prerequisites: All networking features require ASIO. See the ASIO Setup Guide for installation instructions.

## CORS (Cross-Origin Resource Sharing)

### Quick CORS Setup

    #include "glaze/net/http_server.hpp"

    glz::http_server server;

    // Enable CORS with default settings (allows all origins)
    server.enable_cors();

    // Good for development, allows all origins and methods
    server.bind(8080).with_signals();
    server.start();
    server.wait_for_signal(); // Block until Ctrl+C

### Production CORS Configuration

    // Restrict CORS to specific origins
    std::vector<std::string> allowed_origins = {
        "https://myapp.com",
        "https://api.myapp.com",
        "https://admin.myapp.com"
    };

    server.enable_cors(allowed_origins, true); // Allow credentials

    // Or use detailed configuration
    glz::cors_config config;
    config.allowed_origins = {"https://myapp.com", "https://app.myapp.com"};
    config.allowed_origins_validator = [](std::string_view origin) {
        return origin.ends_with(".partner.myapp.com");
    };
    config.allowed_methods = {"GET", "POST", "PUT", "DELETE"};
    config.allowed_headers = {"Content-Type", "Authorization", "X-API-Key"};
    config.exposed_headers = {"X-Total-Count", "X-Page-Info"};
    config.allow_credentials = true;
    config.max_age = 3600; // 1 hour preflight cache
    config.options_success_status = 200; // Use legacy-friendly 200 instead of 204

    server.enable_cors(config);

### Custom CORS Middleware

    // Create custom CORS handler
    auto custom_cors = glz::create_cors_middleware({
        .allowed_origins = {"https://trusted-site.com"},
        .allowed_origins_validator = [](std::string_view origin) {
            return origin.ends_with(".trusted-site.com");
        },
        .allowed_methods = {"GET", "POST"},
        .allowed_headers = {"Content-Type", "Authorization"},
        .allow_credentials = true,
        .max_age = 86400 // 24 hours
    });

    server.use(custom_cors);

### CORS Testing Endpoint

    server.get("/test-cors", [](const glz::request& req, glz::response& res) {
        res.json({
            {"message", "CORS test endpoint"},
            {"origin", req.headers.count("Origin") ? req.headers.at("Origin") : "none"},
            {"method", glz::to_string(req.method)},
            {"timestamp", std::time(nullptr)}
        });
    });

### Automatic Preflight Handling

The server builds the `Allow` header dynamically, and runs middleware (including CORS) before returning a response. If the origin is denied the preflight receives `403`; if the browser asks for a verb that is not implemented the server answers with `405` (after running middleware so diagnostics still flow); otherwise the CORS middleware fills in the appropriate `Access-Control-Allow-*` headers.

### Extended CORS Configuration

- Dynamic origins – `allowed_origins_validator` let you accept wildcard domains or perform per-request checks.
- Custom preflight status – override `options_success_status` if a client expects `200` instead of `204`.

## WebSocket Support

### Basic WebSocket Server

    #include "glaze/net/websocket_connection.hpp"

    // Create WebSocket server
    auto ws_server = std::make_shared<glz::websocket_server>();

    // Handle new connections
    ws_server->on_open([](std::shared_ptr<glz::websocket_connection> conn, const glz::request& req) {
        std::cout << "WebSocket connection opened from " << conn->remote_address() << std::endl;
        conn->send_text("Welcome to the WebSocket server!");
    });

    // Handle incoming messages
    ws_server->on_message([](std::shared_ptr<glz::websocket_connection> conn, std::string_view message, glz::ws_opcode opcode) {
        if (opcode == glz::ws_opcode::text) {
            std::cout << "Received: " << message << std::endl;

            // Echo message back
            conn->send_text("Echo: " + std::string(message));
        }
    });

    // Handle connection close
    ws_server->on_close([](std::shared_ptr<glz::websocket_connection> conn) {
        std::cout << "WebSocket connection closed" << std::endl;
    });

    // Handle errors
    ws_server->on_error([](std::shared_ptr<glz::websocket_connection> conn, std::error_code ec) {
        std::cerr << "WebSocket error: " << ec.message() << std::endl;
    });

    // Mount WebSocket on HTTP server
    glz::http_server server;
    server.websocket("/ws", ws_server);

    server.bind(8080).with_signals();
    server.start();
    server.wait_for_signal();

WebSocket routes also support `:param` segments and can be registered on an `http_router` and mounted, e.g. `router.websocket("/rooms/:room/ws", ws_server)`. The captured value is available in `request.params` inside `on_open`. See HTTP Router for details.

### Chat Room Example

    class ChatRoom {
        std::set<std::shared_ptr<glz::websocket_connection>> connections_;
        std::mutex connections_mutex_;

    public:
        void add_connection(std::shared_ptr<glz::websocket_connection> conn) {
            std::lock_guard<std::mutex> lock(connections_mutex_);
            connections_.insert(conn);
        }

        void remove_connection(std::shared_ptr<glz::websocket_connection> conn) {
            std::lock_guard<std::mutex> lock(connections_mutex_);
            connections_.erase(conn);
        }

        void broadcast_message(const std::string& message) {
            std::lock_guard<std::mutex> lock(connections_mutex_);

            auto it = connections_.begin();
            while (it != connections_.end()) {
                auto conn = *it;
                try {
                    conn->send_text(message);
                    ++it;
                } catch (...) {
                    // Remove broken connections
                    it = connections_.erase(it);
                }
            }
        }
    };

    // Setup chat WebSocket
    ChatRoom chat_room;
    auto chat_server = std::make_shared<glz::websocket_server>();

    chat_server->on_open([&chat_room](auto conn, const glz::request& req) {
        chat_room.add_connection(conn);
        chat_room.broadcast_message("User joined the chat");
    });

    chat_server->on_message([&chat_room](auto conn, std::string_view message, glz::ws_opcode opcode) {
        if (opcode == glz::ws_opcode::text) {
            chat_room.broadcast_message(std::string(message));
        }
    });

    chat_server->on_close([&chat_room](auto conn) {
        chat_room.remove_connection(conn);
        chat_room.broadcast_message("User left the chat");
    });

    server.websocket("/chat", chat_server);

### WebSocket Authentication

    auto secure_ws = std::make_shared<glz::websocket_server>();

    // Validate connections before upgrade
    secure_ws->on_validate([](const glz::request& req) -> bool {
        auto auth_header = req.headers.find("Authorization");
        if (auth_header == req.headers.end()) {
            return false;
        }

        return validate_jwt_token(auth_header->second);
    });

    secure_ws->on_open([](auto conn, const glz::request& req) {
        // Store user info from validated token
        auto user_data = extract_user_from_token(req.headers.at("Authorization"));
        conn->set_user_data(std::make_shared<UserData>(user_data));

        conn->send_text("Authenticated successfully");
    });

    server.websocket("/secure-ws", secure_ws);

## SSL/TLS Support

### HTTPS Server Setup

    #include "glaze/net/http_server.hpp"

    // Create HTTPS server (EnableTLS template parameter)
    glz::https_server server;  // or glz::http_server<true>

    // Load SSL certificates
    server.load_certificate("server-cert.pem", "server-key.pem");

    // Configure SSL options
    server.set_ssl_verify_mode(0); // No client certificate verification

    // Optional: Require client certificates
    // server.set_ssl_verify_mode(SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT);

    // Setup routes as normal
    server.get("/secure-data", [](const glz::request& req, glz::response& res) {
        res.json({
            {"secure", true},
            {"client_ip", req.remote_ip},
            {"timestamp", std::time(nullptr)}
        });
    });

    server.bind(8443).with_signals(); // Standard HTTPS port
    server.start();
    server.wait_for_signal();

### Mixed HTTP/HTTPS Server

    // Run both HTTP and HTTPS on different ports
    void run_dual_servers() {
        // HTTP server
        glz::http_server http_server;
        http_server.get("/health", health_check_handler);
        http_server.get("/redirect", [](const glz::request& req, glz::response& res) {
            res.status(301).header("Location", "https://localhost:8443" + req.target);
        });

        // HTTPS server
        glz::https_server https_server;
        https_server.load_certificate("cert.pem", "key.pem");
        setup_secure_routes(https_server);

        // Start both servers
        auto http_future = std::async([&]() {
            http_server.bind(8080);
            http_server.start();
        });

        auto https_future = std::async([&]() {
            https_server.bind(8443);
            https_server.start();
        });

        // Wait for shutdown signal
        wait_for_shutdown();

        http_server.stop();
        https_server.stop();

        http_future.wait();
        https_future.wait();
    }

### Multiple Servers with Shared io_context

You can run multiple servers on the same io_context for better resource efficiency:

    #include "glaze/net/http_server.hpp"
    #include <asio/io_context.hpp>
    #include <memory>
    #include <thread>

    void run_multiple_servers_shared_context() {
        // Create a shared io_context
        auto io_ctx = std::make_shared<asio::io_context>();

        // Create HTTP server on shared context
        glz::http_server http_server(io_ctx);
        http_server.get("/api/v1", [](const glz::request&, glz::response& res) {
            res.json({{"version", "1.0"}});
        });
        http_server.bind(8080);
        http_server.start(0); // Don't create worker threads

        // Create HTTPS server on same shared context
        glz::https_server https_server(io_ctx);
        https_server.load_certificate("cert.pem", "key.pem");
        https_server.get("/api/v2", [](const glz::request&, glz::response& res) {
            res.json({{"version", "2.0"}, {"secure", true}});
        });
        https_server.bind(8443);
        https_server.start(0); // Don't create worker threads

        // Run the shared io_context in a thread pool
        std::vector<std::thread> threads;
        const size_t num_threads = std::thread::hardware_concurrency();
        threads.reserve(num_threads);

        for (size_t i = 0; i < num_threads; ++i) {
            threads.emplace_back([io_ctx]() {
                io_ctx->run();
            });
        }

        std::cout << "HTTP server running on port 8080\n";
        std::cout << "HTTPS server running on port 8443\n";
        std::cout << "Both servers sharing " << num_threads << " worker threads\n";

        // Wait for shutdown...
        wait_for_shutdown();

        // Stop both servers and the io_context
        http_server.stop();
        https_server.stop();
        io_ctx->stop();

        // Wait for all threads to finish
        for (auto& thread : threads) {
            if (thread.joinable()) {
                thread.join();
            }
        }
    }

This approach is useful when you need to: - Run multiple servers (HTTP, HTTPS, WebSocket) on the same thread pool - Minimize resource usage by sharing threads between services - Centralize io_context management for better control - Integrate multiple servers into an existing ASIO-based application

## HTTP Client

### Basic HTTP Requests

    #include "glaze/net/http_client.hpp"

    glz::http_client client;

    // GET request
    auto response = client.get("https://api.example.com/users");
    if (response && response->status_code == 200) {
        std::cout << "Response: " << response->response_body << std::endl;
    }

    // POST request with JSON
    User new_user{0, "John Doe", "john@example.com"};
    auto create_response = client.post_json("https://api.example.com/users", new_user);

    // POST with custom headers
    std::unordered_map<std::string, std::string> headers = {
        {"Authorization", "Bearer " + token},
        {"Content-Type", "application/json"}
    };

    auto auth_response = client.post("https://api.example.com/data",
                                    json_data, headers);

### Async HTTP Requests

    // Async GET
    auto future_response = client.get_async("https://api.example.com/slow-endpoint");

    // Do other work while request is in progress
    perform_other_tasks();

    // Get result when ready
    auto response = future_response.get();
    if (response && response->status_code == 200) {
        process_response(response->response_body);
    }

    // Multiple concurrent requests
    std::vector<std::future<std::expected<glz::response, std::error_code>>> futures;

    for (int i = 0; i < 10; ++i) {
        std::string url = "https://api.example.com/data/" + std::to_string(i);
        futures.push_back(client.get_async(url));
    }

    // Collect all results
    for (auto& future : futures) {
        auto response = future.get();
        if (response) {
            process_response(*response);
        }
    }

## Request/Response Middleware

### Custom Middleware Development

    // Timing middleware
    auto timing_middleware = [](const glz::request& req, glz::response& res) {
        auto start = std::chrono::high_resolution_clock::now();

        // Store start time in response for later use
        res.header("X-Request-Start", std::to_string(
            std::chrono::duration_cast<std::chrono::milliseconds>(
                start.time_since_epoch()).count()));
    };

    // Response time middleware (would need to be applied after handler)
    auto response_time_middleware = [](const glz::request& req, glz::response& res) {
        auto start_header = res.response_headers.find("X-Request-Start");
        if (start_header != res.response_headers.end()) {
            auto start_ms = std::stoll(start_header->second);
            auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()).count();

            res.header("X-Response-Time", std::to_string(now_ms - start_ms) + "ms");
        }
    };

    server.use(timing_middleware);

### Security Middleware

    // Security headers middleware
    auto security_middleware = [](const glz::request& req, glz::response& res) {
        res.header("X-Content-Type-Options", "nosniff");
        res.header("X-Frame-Options", "DENY");
        res.header("X-XSS-Protection", "1; mode=block");
        res.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
        res.header("Content-Security-Policy", "default-src 'self'");
    };

    // Rate limiting middleware
    class RateLimiter {
        std::unordered_map<std::string, std::vector<std::chrono::steady_clock::time_point>> requests_;
        std::mutex mutex_;
        const int max_requests_ = 100;
        const std::chrono::minutes window_{1};

    public:
        glz::handler create_middleware() {
            return [this](const glz::request& req, glz::response& res) {
                std::lock_guard<std::mutex> lock(mutex_);

                auto now = std::chrono::steady_clock::now();
                auto& client_requests = requests_[req.remote_ip];

                // Remove old requests outside the window
                client_requests.erase(
                    std::remove_if(client_requests.begin(), client_requests.end(),
                        [now, this](const auto& time) {
                            return now - time > window_;
                        }),
                    client_requests.end());

                // Check rate limit
                if (client_requests.size() >= max_requests_) {
                    res.status(429).json({{"error", "Rate limit exceeded"}});
                    return;
                }

                client_requests.push_back(now);
            };
        }
    };

    RateLimiter rate_limiter;
    server.use(security_middleware);
    server.use(rate_limiter.create_middleware());

## Performance Optimization

### Connection Pooling

    // The HTTP client automatically manages connections
    class APIClient {
        glz::http_client client_;  // Reuse same client instance

    public:
        // Multiple requests reuse connections automatically
        std::vector<User> get_all_users() {
            auto response = client_.get("https://api.example.com/users");
            // Connection is pooled for reuse

            if (response && response->status_code == 200) {
                return glz::read_json<std::vector<User>>(response->response_body).value_or(std::vector<User>{});
            }
            return {};
        }

        User get_user(int id) {
            auto response = client_.get("https://api.example.com/users/" + std::to_string(id));
            // Reuses pooled connection if available

            if (response && response->status_code == 200) {
                return glz::read_json<User>(response->response_body).value_or(User{});
            }
            return {};
        }
    };

### Async Processing

    // Process requests asynchronously for better throughput
    server.get("/heavy-work", [](const glz::request& req, glz::response& res) {
        // Offload heavy work to thread pool
        auto future = std::async([&]() {
            auto result = perform_heavy_computation();
            res.json(result);
        });

        // Response will be sent when async work completes
        future.wait();
    });

    // Or use the built-in async handlers
    server.get_async("/async-work", [](const glz::request& req, glz::response& res) -> std::future<void> {
        return std::async([&req, &res]() {
            auto data = fetch_data_from_external_api();
            res.json(data);
        });
    });

## Error Handling and Logging

### Structured Error Responses

    struct ErrorResponse {
        std::string error;
        std::string message;
        int code;
        std::string timestamp;
    };

    auto error_handler = [](const std::exception& e, glz::response& res) {
        ErrorResponse error;
        error.timestamp = get_iso_timestamp();

        if (auto validation_error = dynamic_cast<const ValidationException*>(&e)) {
            error.error = "validation_error";
            error.message = validation_error->what();
            error.code = 1001;
            res.status(400);
        } else if (auto not_found = dynamic_cast<const NotFoundException*>(&e)) {
            error.error = "not_found";
            error.message = not_found->what();
            error.code = 1002;
            res.status(404);
        } else {
            error.error = "internal_error";
            error.message = "An unexpected error occurred";
            error.code = 1000;
            res.status(500);
        }

        res.json(error);
    };

### Request Logging

    // Structured logging middleware
    auto logging_middleware = [](const glz::request& req, glz::response& res) {
        auto start_time = std::chrono::high_resolution_clock::now();

        // Log request
        std::cout << "REQUEST: " << glz::to_string(req.method) << " " << req.target
                  << " from " << req.remote_ip << ":" << req.remote_port << std::endl;

        // Store start time for response logging
        res.header("X-Start-Time", std::to_string(
            std::chrono::duration_cast<std::chrono::microseconds>(
                start_time.time_since_epoch()).count()));
    };

    server.use(logging_middleware);