Router Specification
Overview
This document specifies the design of a composable, Express-style router for the C HTTP Server. The router enables modular route organization through nested sub-routers that can be mounted at different path prefixes.
Design Principles
- Lightweight - Uses simple structs and function pointers, no complex abstractions
- Composable - Routers can contain other routers, enabling hierarchical organization
- Modular - Each router can be defined in its own file and composed later
Core Concepts
Router
A router is a flat collection of routes (pattern + handler pairs). Sub-routers are flattened at mount time for zero runtime overhead.
typedef struct Router Router;
struct Router {
char* patterns[MAX_ROUTES]; // Route patterns (regex)
regex_t compiled_patterns[MAX_ROUTES]; // Compiled regex patterns
HandlerFunc handlers[MAX_ROUTES]; // Handler functions
int route_count; // Number of routes
};
Route
A route is a mapping from a URL pattern to a handler function.
typedef int (*HandlerFunc)(ResponseWriter* w, Request* r);
Mounting
Mounting is the process of flattening a sub-router into a parent router at a specific path prefix. All routes in the sub-router are prefixed and copied into the parent at mount time. The child router is freed after mounting.
Key insight: Since C servers configure routes at startup and never change them at runtime, we flatten the route tree at mount time for zero runtime overhead. Request matching becomes a simple O(n) loop with no recursion or pointer chasing.
API Design
Creating Routers
// Create a new router
Router* router_create(void);
// Free a router and all its sub-routers
void router_free(Router* router);
Adding Routes
// Add a route to a router
// pattern: regex pattern (e.g., "^/$", "^/users/(.*)$")
// handler: function to handle matching requests
void router_add(Router* router, const char* pattern, HandlerFunc handler);
Mounting Sub-Routers
// Mount a sub-router at a prefix (flattens immediately)
// parent: the parent router
// prefix: path prefix (e.g., "/api", "/blog")
// child: the sub-router to mount (will be freed after flattening)
void router_mount(Router* parent, const char* prefix, Router* child);
Important: The child router is freed after mounting since all its routes are copied into the parent. Do not use the child router after mounting.
Route Matching
// Find a handler for a given path
// Returns the handler function or NULL if no match
HandlerFunc router_match(Router* router, const char* path, Request* req);
Usage Examples
Basic Routing (Flat)
// Create a simple router
Router* main_router = router_create();
// Add routes
router_add(main_router, "^/$", Index);
router_add(main_router, "^/about$", About);
router_add(main_router, "^/404$", Error404);
// Use it
http.ListenAndServe(hostname, main_router);
Nested Routing (Composable)
// Create a blog router
Router* blog_router = router_create();
router_add(blog_router, "^/$", BlogIndex); // /blog/
router_add(blog_router, "^/post/(.*)$", BlogPost); // /blog/post/:id
router_add(blog_router, "^/archive$", BlogArchive); // /blog/archive
// Create an API router
Router* api_router = router_create();
router_add(api_router, "^/users$", GetUsers); // /api/users
router_add(api_router, "^/posts$", GetPosts); // /api/posts
// Create main router and mount sub-routers
Router* main_router = router_create();
router_add(main_router, "^/$", Index);
router_add(main_router, "^/about$", About);
router_mount(main_router, "/blog", blog_router); // with the /blog prefix
router_mount(main_router, "/api", api_router); // with the /api prefix
// Final route structure:
// / -> Index
// /about -> About
// /blog/ -> BlogIndex
// /blog/post/123 -> BlogPost
// /blog/archive -> BlogArchive
// /api/users -> GetUsers
// /api/posts -> GetPosts
Modular Organization
// src/app/blog/routes.c
Router* create_blog_routes(void) {
Router* r = router_create();
router_add(r, "^/$", BlogIndex);
router_add(r, "^/post/(.*)$", BlogPost);
router_add(r, "^/archive$", BlogArchive);
router_add(r, "^/tags$", BlogTags);
return r;
}
// src/app/api/routes.c
Router* create_api_routes(void) {
Router* r = router_create();
router_add(r, "^/users$", GetUsers);
router_add(r, "^/posts$", GetPosts);
router_add(r, "^/comments$", GetComments);
return r;
}
// src/app/admin/routes.c
Router* create_admin_routes(void) {
Router* r = router_create();
router_add(r, "^/$", AdminDashboard);
router_add(r, "^/users$", ManageUsers);
router_add(r, "^/settings$", Settings);
return r;
}
// src/main.c
int main() {
Router* main = router_create();
router_add(main, "^/$", Index);
router_add(main, "^/about$", About);
router_add(main, "^/404$", Error404);
// Mount sub-routers
router_mount(main, "/blog", create_blog_routes());
router_mount(main, "/api", create_api_routes());
router_mount(main, "/admin", create_admin_routes());
http.ListenAndServe(hostname, main);
return 0;
}
Route Matching Algorithm
Since routes are flattened at mount time, matching is a simple linear search:
- Iterate through routes - Loop through the flat array of patterns
- Test each pattern - Use regex to match against the request path
- Return first match - Return the handler for the first matching pattern
- Return NULL if no match - No route matched the request
Performance: O(n) where n is the total number of routes. No recursion, no pointer chasing, cache-friendly sequential access.
NOTE
When mounting a sub-router, patterns are prefixed and flattened into the parent:
Conclusion
This router design provides a clean, composable way to organize routes in a C HTTP server while maintaining maximum performance. It is a model of nested routers during setup, but flattens everything at mount time for zero runtime overhead.
Key insight: C servers configure routes at startup and never change them. By flattening the route tree at mount time, we get the best of both worlds:
- Development time: Composable, modular, reusable routers
- Runtime: Simple O(n) loop, no recursion, cache-friendly
The implementation is straightforward, requiring only basic struct manipulation, string operations, and regex compilation. No complex algorithms, no hidden costs, just fast, predictable route matching.
Router Specification
Overview
This document specifies the design of a composable, Express-style router for the C HTTP Server. The router enables modular route organization through nested sub-routers that can be mounted at different path prefixes.
Design Principles
Core Concepts
Router
A router is a flat collection of routes (pattern + handler pairs). Sub-routers are flattened at mount time for zero runtime overhead.
Route
A route is a mapping from a URL pattern to a handler function.
Mounting
Mounting is the process of flattening a sub-router into a parent router at a specific path prefix. All routes in the sub-router are prefixed and copied into the parent at mount time. The child router is freed after mounting.
Key insight: Since C servers configure routes at startup and never change them at runtime, we flatten the route tree at mount time for zero runtime overhead. Request matching becomes a simple O(n) loop with no recursion or pointer chasing.
API Design
Creating Routers
Adding Routes
Mounting Sub-Routers
Important: The child router is freed after mounting since all its routes are copied into the parent. Do not use the child router after mounting.
Route Matching
Usage Examples
Basic Routing (Flat)
Nested Routing (Composable)
Modular Organization
Route Matching Algorithm
Since routes are flattened at mount time, matching is a simple linear search:
Performance: O(n) where n is the total number of routes. No recursion, no pointer chasing, cache-friendly sequential access.
NOTE
When mounting a sub-router, patterns are prefixed and flattened into the parent:
Conclusion
This router design provides a clean, composable way to organize routes in a C HTTP server while maintaining maximum performance. It is a model of nested routers during setup, but flattens everything at mount time for zero runtime overhead.
Key insight: C servers configure routes at startup and never change them. By flattening the route tree at mount time, we get the best of both worlds:
The implementation is straightforward, requiring only basic struct manipulation, string operations, and regex compilation. No complex algorithms, no hidden costs, just fast, predictable route matching.