Skip to content

HTTP Server

The Glaze HTTP server provides a high-performance, async HTTP server implementation using ASIO.

Basic Usage

Creating a Server

#include "glaze/net/http_server.hpp"

glz::http_server server;

// Configure routes
server.get("/hello", [](const glz::request& req, glz::response& res) {
    res.body("Hello, World!");
});

// Start server
server.bind("127.0.0.1", 8080);
server.start();

HTTPS Server

#include "glaze/net/http_server.hpp"

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

// Load SSL certificates
server.load_certificate("cert.pem", "key.pem");
server.set_ssl_verify_mode(0); // No client verification

server.get("/secure", [](const glz::request& req, glz::response& res) {
    res.json({{"secure", true}, {"message", "This is HTTPS!"}});
});

server.bind(8443);
server.start();

Server Configuration

Binding and Ports

// Bind to specific address and port
server.bind("127.0.0.1", 8080);

// Bind to all interfaces
server.bind("0.0.0.0", 8080);

// Bind to port only (defaults to all interfaces)
server.bind(8080);

// IPv6 support
server.bind("::1", 8080);  // localhost IPv6
server.bind("::", 8080);   // all IPv6 interfaces

Thread Pool Configuration

// Start with specific number of threads
server.start(4);  // 4 worker threads

// Start with hardware concurrency (default)
server.start();   // Uses std::thread::hardware_concurrency()

Error Handling

server.on_error([](std::error_code ec, std::source_location loc) {
    std::fprintf(stderr, "Server error at %s:%d: %s\n", 
                loc.file_name(), loc.line(), ec.message().c_str());
});

Route Registration

Basic Routes

// GET route
server.get("/users", [](const glz::request& req, glz::response& res) {
    res.json(get_all_users());
});

// POST route
server.post("/users", [](const glz::request& req, glz::response& res) {
    User user;
    if (auto ec = glz::read_json(user, req.body)) {
        res.status(400).body("Invalid JSON");
        return;
    }
    create_user(user);
    res.status(201).json(user);
});

// Multiple HTTP methods
server.route(glz::http_method::PUT, "/users/:id", handler);
server.route(glz::http_method::DELETE, "/users/:id", handler);

Route Parameters

// Path parameters with :param syntax
server.get("/users/:id", [](const glz::request& req, glz::response& res) {
    int user_id = std::stoi(req.params.at("id"));
    res.json(get_user_by_id(user_id));
});

// Multiple parameters
server.get("/users/:user_id/posts/:post_id", 
    [](const glz::request& req, glz::response& res) {
        int user_id = std::stoi(req.params.at("user_id"));
        int post_id = std::stoi(req.params.at("post_id"));
        res.json(get_user_post(user_id, post_id));
    });

// Wildcard parameters with *param syntax
server.get("/files/*path", [](const glz::request& req, glz::response& res) {
    std::string file_path = req.params.at("path");
    serve_static_file(file_path, res);
});

Parameter Constraints

// Numeric ID constraint
glz::http_router::param_constraint id_constraint{
    .pattern = "[0-9]+",
    .description = "User ID must be numeric"
};

server.get("/users/:id", handler, {{"id", id_constraint}});

// Email constraint
glz::http_router::param_constraint email_constraint{
    .pattern = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
    .description = "Valid email address required"
};

Response Handling

Response Builder

server.get("/api/data", [](const glz::request& req, glz::response& res) {
    // Set status code
    res.status(200);

    // Set headers
    res.header("X-Custom-Header", "value");
    res.content_type("application/json");

    // Set body
    res.body("{\"message\": \"Hello\"}");

    // Method chaining
    res.status(201)
       .header("Location", "/api/data/123")
       .json({{"id", 123}, {"created", true}});
});

JSON Responses

// Automatic JSON serialization
struct User {
    int id;
    std::string name;
    std::string email;
};

server.get("/users/:id", [](const glz::request& req, glz::response& res) {
    User user = get_user(std::stoi(req.params.at("id")));
    res.json(user);  // Automatically serializes to JSON
});

// Using glz::opts for custom serialization
server.get("/users/:id/detailed", [](const glz::request& req, glz::response& res) {
    User user = get_user(std::stoi(req.params.at("id")));
    res.body<glz::opts{.prettify = true}>(user);  // Pretty-printed JSON
});

Error Responses

server.get("/users/:id", [](const glz::request& req, glz::response& res) {
    try {
        int id = std::stoi(req.params.at("id"));
        User user = get_user(id);

        if (user.id == 0) {
            res.status(404).json({{"error", "User not found"}});
            return;
        }

        res.json(user);
    } catch (const std::exception& e) {
        res.status(400).json({{"error", "Invalid user ID"}});
    }
});

Middleware

Adding Middleware

// Logging middleware
server.use([](const glz::request& req, glz::response& res) {
    std::cout << glz::to_string(req.method) << " " << req.target << std::endl;
});

// Authentication middleware
server.use([](const glz::request& req, glz::response& res) {
    if (req.headers.find("Authorization") == req.headers.end()) {
        res.status(401).json({{"error", "Authorization required"}});
        return;
    }
    // Validate token...
});

// CORS middleware (built-in)
server.enable_cors();

Custom Middleware

// Rate limiting middleware
auto rate_limiter = [](const glz::request& req, glz::response& res) {
    static std::unordered_map<std::string, int> request_counts;

    auto& count = request_counts[req.remote_ip];
    if (++count > 100) {  // 100 requests per IP
        res.status(429).json({{"error", "Rate limit exceeded"}});
        return;
    }
};

server.use(rate_limiter);

Mounting Sub-routers

// Create sub-router for API v1
glz::http_router api_v1;
api_v1.get("/users", get_users_handler);
api_v1.post("/users", create_user_handler);

// Create sub-router for API v2  
glz::http_router api_v2;
api_v2.get("/users", get_users_v2_handler);

// Mount sub-routers
server.mount("/api/v1", api_v1);
server.mount("/api/v2", api_v2);

// Now routes are available at:
// GET /api/v1/users
// POST /api/v1/users  
// GET /api/v2/users

Static File Serving

// Serve static files from directory
server.get("/static/*path", [](const glz::request& req, glz::response& res) {
    std::string file_path = "public/" + req.params.at("path");

    std::ifstream file(file_path);
    if (!file.is_open()) {
        res.status(404).body("File not found");
        return;
    }

    std::string content((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());

    // Set appropriate content type
    if (file_path.ends_with(".html")) {
        res.content_type("text/html");
    } else if (file_path.ends_with(".css")) {
        res.content_type("text/css");
    } else if (file_path.ends_with(".js")) {
        res.content_type("application/javascript");
    }

    res.body(content);
});

Server Lifecycle

Starting and Stopping

class APIServer {
    glz::http_server server_;
    std::future<void> server_future_;

public:
    bool start(uint16_t port) {
        try {
            configure_routes();
            server_.bind(port);

            // Start in background thread
            server_future_ = std::async(std::launch::async, [this]() {
                server_.start();
            });

            return true;
        } catch (const std::exception& e) {
            std::cerr << "Failed to start server: " << e.what() << std::endl;
            return false;
        }
    }

    void stop() {
        server_.stop();
        if (server_future_.valid()) {
            server_future_.wait();
        }
    }

private:
    void configure_routes() {
        server_.get("/health", [](const glz::request&, glz::response& res) {
            res.json({{"status", "healthy"}});
        });
    }
};

Graceful Shutdown

#include <csignal>

std::atomic<bool> running{true};
glz::http_server server;

void signal_handler(int signal) {
    std::cout << "Received signal " << signal << ", shutting down..." << std::endl;
    running = false;
    server.stop();
}

int main() {
    // Register signal handlers
    std::signal(SIGINT, signal_handler);
    std::signal(SIGTERM, signal_handler);

    // Configure server
    setup_routes(server);
    server.bind(8080);

    // Start server
    std::future<void> server_future = std::async(std::launch::async, [&]() {
        server.start();
    });

    // Wait for shutdown signal
    while (running) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    server_future.wait();
    return 0;
}

Production Configuration

Performance Tuning

// Optimize for production
server.bind("0.0.0.0", 8080);

// Use multiple threads (typically CPU cores)
int num_threads = std::thread::hardware_concurrency();
server.start(num_threads);

// Configure error handling for production
server.on_error([](std::error_code ec, std::source_location loc) {
    // Log to file/service instead of stderr
    if (should_log_error(ec)) {
        log_error(ec, loc);
    }
});

Health Checks

// Health check endpoint
server.get("/health", [](const glz::request&, glz::response& res) {
    // Check database connection, external services, etc.
    bool healthy = check_database() && check_external_apis();

    if (healthy) {
        res.json({
            {"status", "healthy"},
            {"timestamp", get_timestamp()},
            {"version", VERSION}
        });
    } else {
        res.status(503).json({
            {"status", "unhealthy"},
            {"timestamp", get_timestamp()}
        });
    }
});

// Readiness check for Kubernetes
server.get("/ready", [](const glz::request&, glz::response& res) {
    if (is_ready_to_serve()) {
        res.json({{"status", "ready"}});
    } else {
        res.status(503).json({{"status", "not ready"}});
    }
});