Repository navigation
Accumulate-during-lookup model for interceptors #56
Closed
SeanTAllen
started this conversation in
Research
Replies: 1 comment
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Request interceptors and response interceptors both cascade through route groups today — app-level interceptors get prepended to group-level, which get prepended to route-level. The concatenation happens at registration time in
Application.serve()via_ConcatInterceptorsand_ConcatResponseInterceptors. Each route in the router ends up with its full flattened interceptor array baked in.This works, but it has a gap: 404 responses. When no route matches, there's no
_RouteMatchto carry interceptors. Currently, app-level response interceptors are passed separately through_Listener→_Connectionfor the 404 path. Request interceptors don't run on 404s at all.The radix tree already encodes the route hierarchy. A lookup for
/api/public/doesnt_existtraverses root →/api→/publicbefore failing to find/doesnt_exist. Every interceptor registered along that partial path should still apply. An auth interceptor on/apishould reject unauthenticated requests to/api/nonexistent— otherwise you leak API structure to unauthenticated users. A CORS response interceptor on/apishould add headers to 404s under/api.The proposed model: interceptors accumulate during tree traversal rather than being flattened at registration time. During
_lookup, each node the traversal visits contributes its interceptors to a running accumulation. On a successful match, the handler gets the full accumulated set. On a failed match (404), the partial traversal still produces the interceptors accumulated up to the point of failure.This applies to both request and response interceptors. The user-facing registration API doesn't change — you still register interceptors at the app, group, and route level. The difference is internal: instead of pre-concatenating everything into the leaf nodes at build time, the tree nodes carry their own interceptors and the lookup assembles them.
What changes
Router internals:
_BuildNodeand_TreeNodestore interceptors at each node, not just at leaves._lookupaccumulates interceptors as it descends. The return type needs to carry accumulated interceptors even on a failed match — probably a new type alongside_RouteMatchfor partial matches, or a restructured return that always includes interceptors._Connection: The 404 path uses the accumulated interceptors from the partial traversal instead of a separate app-level copy. The_Listenerno longer needs to carry response interceptors._ConcatInterceptors/_ConcatResponseInterceptors: Still needed for group flattening at registration time (groups flatten into their parent's route list). ButApplication.serve()no longer pre-concatenates app-level interceptors onto every route — it stores them on the root node instead.What doesn't change
The user-facing API is identical.
add_request_interceptor(),add_response_interceptor(), group-level interceptors, per-route interceptors — all the same. This is purely an implementation change that fixes the 404 gap.Current state
Response interceptors shipped with the flatten-at-registration model. Request interceptors also use flatten-at-registration. Both are compatible with the accumulate-during-lookup model — the change is additive (fixing 404 coverage) without breaking existing behavior for matched routes.
All reactions