feat(http): give ListenerHTTP the same fallback and query-strip routing
ListenerHTTP1's docs promise the same route map shape as ListenerHTTP so a handler can be registered with both and served over either protocol. That only holds if the dispatch rule is the same on both, so mirror it here: exact `:path`, then the query-stripped path, then `fallback`, then the synthetic 404. The query-strip half is a behaviour change on this listener. `/thing?x=1` previously 404'd even with `/thing` registered, while HTTP/1.1 routed it — the asymmetry the shared route map was supposed to avoid. It also matters for `fallback`: without it a query string would divert a registered path to the fallback over HTTP/3 but not over HTTP/1.1. `fallback` covers `routes` only. An unmatched WebTransport CONNECT is still a 404 — a WT handler takes a session, not a request, so there is nothing sensible to hand it. MakeBidiHandler now reads the maps off `self` instead of taking them as pointer parameters. `self` was already captured and unused, and this mirrors how ListenerHTTP1 reaches its own state through Impl::owner.
This commit is contained in:
parent
4d5ef7c96b
commit
7a524cfdd0
2 changed files with 101 additions and 19 deletions
|
|
@ -15,9 +15,11 @@ namespace Crafter {
|
|||
// through the route map, and writes a response back on the same bidi
|
||||
// stream. ALPN is fixed to "h3".
|
||||
//
|
||||
// Routes are keyed by `:path` (exact match). Unknown paths return a
|
||||
// synthetic 404. Route handlers run on the ThreadPool — multiple requests
|
||||
// on the same connection can therefore execute concurrently.
|
||||
// Routes are keyed by `:path`, matched exactly and then with the query
|
||||
// string stripped. A path matching nothing goes to `fallback` if one is
|
||||
// set, and returns a synthetic 404 otherwise. Route handlers run on the
|
||||
// ThreadPool — multiple requests on the same connection can therefore
|
||||
// execute concurrently.
|
||||
//
|
||||
// WebTransport: pass a non-empty `wtRoutes` to additionally accept
|
||||
// extended-CONNECT requests (`:method=CONNECT, :protocol=webtransport`)
|
||||
|
|
@ -33,6 +35,19 @@ namespace Crafter {
|
|||
// straightforward.
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes;
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes;
|
||||
|
||||
// Called for any request whose `:path` matches no entry in `routes`,
|
||||
// with the full path still in `request.path`. Lets a caller route
|
||||
// paths that cannot be enumerated up front — `/order/<token>`,
|
||||
// `/shop/<slug>` — with its own matcher, instead of this class
|
||||
// imposing a pattern syntax. Leave unset to keep synthesising a 404.
|
||||
// Applies to `routes` only: an unmatched WebTransport CONNECT is
|
||||
// still a 404, since a WT handler has a different signature.
|
||||
//
|
||||
// Must be assigned before Listen(); stream handlers read it without
|
||||
// synchronisation. ListenerAsyncHTTP starts listening in its
|
||||
// constructor, so pass the fallback to that constructor instead.
|
||||
std::function<HTTPResponse(const HTTPRequest&)> fallback;
|
||||
std::string alpn;
|
||||
|
||||
ListenerHTTP(std::uint16_t port,
|
||||
|
|
@ -46,6 +61,19 @@ namespace Crafter {
|
|||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes);
|
||||
|
||||
// Fallback-aware overloads, for callers that construct and Listen()
|
||||
// in one step (and for ListenerAsyncHTTP, which has to).
|
||||
ListenerHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::function<HTTPResponse(const HTTPRequest&)> fallback);
|
||||
|
||||
ListenerHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes,
|
||||
std::function<HTTPResponse(const HTTPRequest&)> fallback);
|
||||
|
||||
~ListenerHTTP();
|
||||
ListenerHTTP(const ListenerHTTP&) = delete;
|
||||
ListenerHTTP(ListenerHTTP&&) noexcept;
|
||||
|
|
@ -78,6 +106,20 @@ namespace Crafter {
|
|||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes);
|
||||
|
||||
// Fallback-aware overloads. The accept loop starts inside these
|
||||
// constructors, so a fallback has to be installed here rather than
|
||||
// assigned to `listener.fallback` afterwards.
|
||||
ListenerAsyncHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::function<HTTPResponse(const HTTPRequest&)> fallback);
|
||||
|
||||
ListenerAsyncHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes,
|
||||
std::function<HTTPResponse(const HTTPRequest&)> fallback);
|
||||
|
||||
~ListenerAsyncHTTP();
|
||||
void Stop();
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue