Skip to content

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)

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.

from router import Router
from 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 == 200
@router.get("/about")
@router.get("/users/list")
@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"}
@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.

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.

When multiple patterns could match a path, the router applies this priority (highest first):

  1. Static — exact segment match
  2. Parameter:name segment
  3. Wildcard*name segment

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/c

Returned when no route pattern matches the request path.

Default body: not found: <METHOD> <PATH>

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>)

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""),
)
MethodDescription
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.routesList of all registered Route objects.

dispatch() mutates the passed Request to set:

  • req.path_params: dict[str, str] — extracted path parameters

Standard response with status, headers, and body fields.

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.

  • 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.
Terminal window
# Unit tests
python3 -m pytest tests/test_router.py
# Property-based tests
python3 -m pytest tests/test_router_properties.py

The 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.