Add a fallback handler for routes that cannot be enumerated #5
No reviewers
Labels
No labels
claude:blocked
claude:done
claude:failed
claude:in-progress
claude:ready
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Catcrafts/Crafter.Network!5
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/issue-4"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the optional
fallbackmember proposed in #4 to bothListenerHTTP1andListenerHTTP, and makes the two dispatch identically.Resolves #4
What changed
std::function<HTTPResponse(const HTTPRequest&)> fallbackon both listeners, called for any request the route map missed, with the full target still inrequest.path. Precedence is the same on each:path/:pathfallback, if setA default-constructed
fallbackis empty, so existing behaviour is unchanged and no call site moves. Took the hook rather than a pattern syntax, per the issue: a consumer with its ownParseRoutekeeps using it, and there is no second route table to disagree with the first.Three things worth a look
A behaviour change on
ListenerHTTP. It now matches the query-stripped path too./thing?x=1previously 404'd on HTTP/3 even with/thingregistered, while HTTP/1.1 routed it — exactly the asymmetry the shared route map is supposed to prevent. It also matters for this feature: without it, a query string would divert a registered path to the fallback over HTTP/3 but not over HTTP/1.1. Worth flagging in case anything depends on the old 404.Constructor overloads on the
ListenerAsync*wrappers.fallbackis a plain public member, fine to assign beforeListen(). But the async wrappers start accepting inside their constructor, solistener.fallback = fafterwards races the accept loop. They take it as a trailing constructor argument instead. The sync classes get matching overloads for symmetry.Scope.
fallbackcoversroutesonly; an unmatched WebTransport CONNECT is still a 404, since a WT handler takes a session rather than a request.PathWithoutQuerymoved out of the HTTP/1.1 implementation into:HTTPas an exportedPathWithoutQueryHTTP— both listeners need it now, and a fallback handler almost always wants the same split.Testing
New
ShouldFallbackUnknownRoutesstates the symmetry property directly: one route map plus one fallback, registered with both listeners, asked the same eight questions, asserting identical answers. Covers exact routes beating the fallback, query strings routing to the bare path, the fallback seeing the full target including the query, the fallback choosing its own status (404 and 303), a throwing fallback becoming a 500, and an unset fallback still producing the listener's own 404.Confirmed it actually bites: removing the fallback lookup from the HTTP/3 dispatch fails 9 checks, removing the query-strip fails 2.
crafter-build test— 14 passed, plusShouldSendwhich times out. That one fetchescloudflare-quic.com:443over UDP/443, which this sandbox blocks at the network-namespace level (sendto→EPERM); it times out identically on a pristinemastercheckout, and the README already notes it needs outbound UDP/443. Nothing in this branch touchesClientHTTPor the QUIC transport.Also verified
crafter-build --target=wasm32-wasip1still compiles (:HTTPis in the browser build) and thatexamples/SimpleClient— a real 4-argumentListenerAsyncHTTPcaller — is unaffected by the new overloads.🤖 Generated with Claude Code