Skip to content

HTTP Router

The Glaze HTTP router provides efficient path matching using a radix tree data structure, supporting static routes, parameterized routes, wildcards, and parameter validation.

Overview

The router supports: - Static routes - Exact path matching - Parameter routes - Dynamic path segments with :param - Wildcard routes - Catch-all segments with *param
- Parameter constraints - Pattern validation for parameters - Middleware - Cross-cutting request/response processing - Efficient matching - O(log n) performance using radix tree

Basic Routing

Static Routes

glz::http_router router;

// Simple static routes
router.get("/", home_handler);
router.get("/about", about_handler);
router.get("/contact", contact_handler);

// Nested static routes
router.get("/api/v1/status", status_handler);
router.get("/api/v1/health", health_handler);

HTTP Methods

// Standard HTTP methods
router.get("/users", get_users);           // GET
router.post("/users", create_user);        // POST
router.put("/users/:id", update_user);     // PUT
router.del("/users/:id", delete_user);     // DELETE
router.patch("/users/:id", patch_user);    // PATCH

// Generic route method
router.route(glz::http_method::HEAD, "/users", head_users);
router.route(glz::http_method::OPTIONS, "/users", options_users);

Parameter Routes

Path Parameters

// Single parameter
router.get("/users/:id", [](const glz::request& req, glz::response& res) {
    std::string user_id = req.params.at("id");
    res.json(get_user(user_id));
});

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

// Mixed static and parameter segments
router.get("/api/v1/users/:id/profile", profile_handler);

Parameter Constraints

// Numeric constraint
glz::http_router::param_constraint numeric{
    .pattern = "[0-9]+",
    .description = "Must be a positive integer"
};

router.get("/users/:id", user_handler, {{"id", numeric}});

// UUID constraint
glz::http_router::param_constraint uuid{
    .pattern = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
    .description = "Must be a valid UUID"
};

router.get("/sessions/:session_id", session_handler, {{"session_id", uuid}});

// Alphanumeric constraint
glz::http_router::param_constraint username{
    .pattern = "[a-zA-Z0-9_]{3,20}",
    .description = "Username: 3-20 alphanumeric characters or underscore"
};

router.get("/profile/:username", profile_handler, {{"username", username}});

Advanced Pattern Matching

The constraint pattern support includes:

// Wildcards
glz::http_router::param_constraint any_extension{
    .pattern = "*.txt",  // Files ending in .txt
    .description = "Text files only"
};

// Character classes
glz::http_router::param_constraint hex_color{
    .pattern = "#[0-9a-fA-F]{6}",  // Hex color codes
    .description = "Valid hex color code"
};

// Anchors
glz::http_router::param_constraint exact_match{
    .pattern = "^admin$",  // Exact match for "admin"
    .description = "Must be exactly 'admin'"
};

// Ranges
glz::http_router::param_constraint year{
    .pattern = "[2][0-9][0-9][0-9]",  // Years starting with 2
    .description = "4-digit year starting with 2"
};

Wildcard Routes

Catch-All Parameters

// Static file serving
router.get("/static/*path", [](const glz::request& req, glz::response& res) {
    std::string file_path = req.params.at("path");
    // file_path contains everything after /static/
    serve_file("public/" + file_path, res);
});

// API versioning catch-all
router.get("/api/*version", [](const glz::request& req, glz::response& res) {
    std::string version = req.params.at("version");
    // Handle all API versions dynamically
    handle_api_request(version, req, res);
});

Wildcard Constraints

// Constrain wildcard content
glz::http_router::param_constraint safe_path{
    .pattern = "[a-zA-Z0-9/._-]+",  // Safe file path characters
    .description = "Safe file path"
};

router.get("/files/*path", file_handler, {{"path", safe_path}});

Route Priority

The router matches routes in priority order:

  1. Static routes (highest priority)
  2. Parameter routes
  3. Wildcard routes (lowest priority)
// These routes are checked in priority order:
router.get("/users/admin", admin_handler);        // 1. Static (exact match)
router.get("/users/:id", user_handler);           // 2. Parameter
router.get("/users/*action", user_action);        // 3. Wildcard

// Request "/users/admin" matches admin_handler
// Request "/users/123" matches user_handler  
// Request "/users/edit/profile" matches user_action

Middleware

Global Middleware

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

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

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

        if (!validate_token(auth_header->second)) {
            res.status(403).json({{"error", "Invalid token"}});
            return;
        }
    }
});

Middleware Execution Order

// Middleware executes in registration order
router.use(logging_middleware);       // 1. First
router.use(auth_middleware);          // 2. Second  
router.use(rate_limit_middleware);    // 3. Third

// Then route handler executes
router.get("/api/data", data_handler); // 4. Finally

Conditional Middleware

// Apply middleware only to specific paths
router.use([](const glz::request& req, glz::response& res) {
    if (req.target.starts_with("/admin/")) {
        // Admin-only middleware
        if (!is_admin_user(req)) {
            res.status(403).json({{"error", "Admin access required"}});
            return;
        }
    }
});

Route Groups

Manual Grouping

// API v1 routes
void setup_api_v1(glz::http_router& router) {
    router.get("/api/v1/users", get_users_v1);
    router.post("/api/v1/users", create_user_v1);
    router.get("/api/v1/users/:id", get_user_v1);
}

// API v2 routes  
void setup_api_v2(glz::http_router& router) {
    router.get("/api/v2/users", get_users_v2);
    router.post("/api/v2/users", create_user_v2);
    router.get("/api/v2/users/:id", get_user_v2);
}

// Main router setup
glz::http_router router;
setup_api_v1(router);
setup_api_v2(router);

Sub-router Mounting

// Create specialized routers
glz::http_router api_router;
api_router.get("/users", get_users);
api_router.post("/users", create_user);

glz::http_router admin_router;
admin_router.get("/dashboard", admin_dashboard);
admin_router.get("/settings", admin_settings);

// Mount on main server
glz::http_server server;
server.mount("/api", api_router);
server.mount("/admin", admin_router);

// Results in routes:
// GET /api/users
// POST /api/users
// GET /admin/dashboard  
// GET /admin/settings

Async Handlers

Async Route Handlers

// Async handlers return std::future<void>
router.get_async("/slow-data", [](const glz::request& req, glz::response& res) -> std::future<void> {
    return std::async([&req, &res]() {
        // Simulate slow operation
        std::this_thread::sleep_for(std::chrono::seconds(2));

        auto data = fetch_slow_data();
        res.json(data);
    });
});

// Convert regular handler to async
router.route_async(glz::http_method::POST, "/async-upload", 
    [](const glz::request& req, glz::response& res) -> std::future<void> {
        return std::async([&]() {
            process_large_upload(req.body);
            res.status(204);  // No content
        });
    });

Route Debugging

Tree Visualization

glz::http_router router;
// ... add routes ...

// Print the routing tree structure
router.print_tree();

/* Output example:
Radix Tree Structure:
Node[api, endpoint=false, children=1, full_path=/api]
  Node[PARAM:version, endpoint=false, children=0+param, full_path=/api/:version]
    Node[users, endpoint=true, children=0, full_path=/api/:version/users]
      Handlers: GET POST 
      Constraints for GET:
        version: v[0-9]+ (API version like v1, v2)
*/

Route Testing

// Test route matching
auto [handler, params] = router.match(glz::http_method::GET, "/api/v1/users");

if (handler) {
    std::cout << "Route matched!" << std::endl;
    for (const auto& [key, value] : params) {
        std::cout << key << " = " << value << std::endl;
    }
} else {
    std::cout << "No route matched" << std::endl;
}

Performance Optimization

Route Organization

// Place most frequently accessed routes first
router.get("/", home_handler);           // High traffic
router.get("/api/health", health_check); // Health checks

// Group related routes together
router.get("/api/users", get_users);
router.get("/api/users/:id", get_user);
router.post("/api/users", create_user);

// Place wildcard routes last
router.get("/static/*path", static_files); // Catch-all

Direct Route Optimization

// The router automatically optimizes non-parameterized routes
// These are stored in a direct lookup table for O(1) access:

router.get("/api/status", status_handler);     // O(1) lookup
router.get("/api/health", health_handler);     // O(1) lookup

// Parameterized routes use radix tree for O(log n) lookup:
router.get("/api/users/:id", user_handler);    // O(log n) lookup

Error Handling

Route Handler Errors

router.get("/api/data", [](const glz::request& req, glz::response& res) {
    try {
        auto data = get_data_that_might_throw();
        res.json(data);
    } catch (const database_error& e) {
        res.status(503).json({{"error", "Database unavailable"}});
    } catch (const validation_error& e) {
        res.status(400).json({{"error", e.what()}});
    } catch (const std::exception& e) {
        res.status(500).json({{"error", "Internal server error"}});
    }
});

Route Conflicts

// The router detects and prevents route conflicts:
router.get("/users/:id", user_handler);
router.get("/users/:user_id", another_handler); // Error: parameter name conflict

// This would throw: 
// std::runtime_error("Route conflict: different parameter names at same position")

Best Practices

Parameter Validation

// Always validate parameters from untrusted input
router.get("/users/:id", [](const glz::request& req, glz::response& res) {
    auto id_str = req.params.at("id");

    try {
        int id = std::stoi(id_str);
        if (id <= 0) {
            res.status(400).json({{"error", "User ID must be positive"}});
            return;
        }

        auto user = get_user(id);
        res.json(user);
    } catch (const std::invalid_argument&) {
        res.status(400).json({{"error", "Invalid user ID format"}});
    }
});

Resource-based Routing

// Follow RESTful conventions
router.get("/users", get_users);           // GET /users - list
router.post("/users", create_user);        // POST /users - create
router.get("/users/:id", get_user);        // GET /users/123 - read
router.put("/users/:id", update_user);     // PUT /users/123 - update
router.del("/users/:id", delete_user);     // DELETE /users/123 - delete

// Nested resources
router.get("/users/:id/posts", get_user_posts);
router.post("/users/:id/posts", create_user_post);