Router
HTTP Request Router
Section titled “HTTP Request Router”Status: ✅ Implemented (axon-web#1) Milestone: M14 Algorithm: Radix tree (compressed trie) Complexity: O(k) lookup, where k = path depth (not O(n) in route count)
Overview
Section titled “Overview”The tools/router.py module provides a high-performance HTTP request
router that maps (method, path) pairs to handler functions. Routes
are organized in a radix tree (also called a compressed trie), so
lookups depend only on the number of path segments, not the number
of registered routes.
Quick Start
Section titled “Quick Start”from router import Routerfrom middleware import Request, Response
router = Router()
@router.get("/")def home(req): return Response(200, {}, b"hello")
@router.get("/users/:id")def get_user(req): user_id = req.path_params["id"] return Response(200, {}, f"user {user_id}".encode())
@router.post("/users")def create_user(req): return Response(201, {}, b"created")
resp = router.dispatch(Request("GET", "/users/42", {}, b""))assert resp.status == 200Route Patterns
Section titled “Route Patterns”Static segments
Section titled “Static segments”@router.get("/about")@router.get("/users/list")Parameterized segments
Section titled “Parameterized segments”@router.get("/users/:id")# /users/42 -> path_params = {"id": "42"}
@router.get("/users/:id/posts/:post_id")# /users/7/posts/99 -> path_params = {"id": "7", "post_id": "99"}Wildcard catch-all
Section titled “Wildcard catch-all”@router.get("/static/*filepath")# /static/css/main.css -> path_params = {"filepath": "css/main.css"}Wildcard segments must be the last segment in a pattern. They capture the entire remaining path (including slashes) as a single parameter.
HTTP Methods
Section titled “HTTP Methods”Supported methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
Each method has a decorator:
@router.get(pattern)@router.post(pattern)@router.put(pattern)@router.delete(pattern)@router.patch(pattern)@router.head(pattern)@router.options(pattern)You can also use router.add(method, pattern, handler) directly.
Priority Ordering
Section titled “Priority Ordering”When multiple patterns could match a path, the router applies this priority (highest first):
- Static — exact segment match
- Parameter —
:namesegment - Wildcard —
*namesegment
Example:
@router.get("/users/me") # static — wins for /users/me@router.get("/users/:name") # parameter — matches other /users/X@router.get("/users/*rest") # wildcard — matches /users/a/b/cError Responses
Section titled “Error Responses”404 — Not Found
Section titled “404 — Not Found”Returned when no route pattern matches the request path.
Default body: not found: <METHOD> <PATH>
405 — Method Not Allowed
Section titled “405 — Method Not Allowed”Returned when a route pattern matches the path but doesn’t have a
handler for the requested method. The Allow response header lists
the methods that ARE registered at that path.
Default body: method not allowed: <METHOD> (allowed: <LIST>)
Custom error handlers
Section titled “Custom error handlers”Pass not_found= and method_not_allowed= to the Router
constructor to override the default responses:
router = Router( not_found=lambda req: Response(404, {}, b"¯\\_(ツ)_/¯"), method_not_allowed=lambda req, allowed: Response(405, {"Allow": ", ".join(allowed)}, b""),)API Reference
Section titled “API Reference”Router
Section titled “Router”| Method | Description |
|---|---|
Router(not_found=None, method_not_allowed=None) | Construct a router with optional custom error handlers. |
router.add(method, pattern, handler) | Register a handler. Raises ValueError on duplicate or unsupported method. |
router.get/post/put/delete/patch/head/options(pattern) | Decorator factories for the corresponding HTTP method. |
router.match(method, path) | Find a handler. Returns (handler, path_params, allowed_methods). |
router.dispatch(req) | Dispatch a Request, return a Response. Populates req.path_params. |
router.routes | List of all registered Route objects. |
Request (from tools/middleware.py)
Section titled “Request (from tools/middleware.py)”dispatch() mutates the passed Request to set:
req.path_params: dict[str, str]— extracted path parameters
Response (from tools/middleware.py)
Section titled “Response (from tools/middleware.py)”Standard response with status, headers, and body fields.
Performance
Section titled “Performance”The radix tree gives O(k) lookup where k = path depth. The
TestPerformance::test_lookup_sublinear_in_route_count test
verifies that registering 100× more routes does not significantly
slow down individual lookups.
Design Decisions
Section titled “Design Decisions”- Zero dependencies — uses only the Python standard library.
- No route compilation step — routes are inserted directly into the tree at registration time.
- Static > param > wildcard priority — matches the convention
used by Go’s
httprouter,chi, and Express. - Wildcard captures rest-of-path as a single string — easier to use than a multi-segment capture for static-file serving.
Testing
Section titled “Testing”# Unit testspython3 -m pytest tests/test_router.py
# Property-based testspython3 -m pytest tests/test_router_properties.pyThe unit tests cover static/param/wildcard matching, method dispatch, 404/405 handling, priority ordering, and edge cases (trailing slash, empty path, etc.). The property tests use Hypothesis to generate random routes and paths, verifying invariants like determinism and the 404/405 mutual exclusion.
See Also
Section titled “See Also”- architecture.md — overall framework architecture
- middleware.md — middleware pipeline (composes with router)
- json-codec.md — JSON request/response codec
- static-server.md — static file serving (uses wildcard routes)
- metrics.md — performance metrics collection