Read request body after route matching and pre-request handler

Previously, for regular handlers the request body was read in routing()
before the route was matched, so the pre-request handler always saw an
already-read body. The ContentReader path, in contrast, ran the
pre-request handler before the body was read. This inconsistency made
it impossible to reject a request (e.g. failed per-route authentication
via req.matched_route) without buffering a potentially large body.

Move the read_content() call into dispatch_request(), after route
matching and the pre-request handler, so both paths behave the same:
route matching -> pre_request_handler -> body read -> handler. A
request rejected by the pre-request handler no longer reads the body
at all; the existing keep-alive drain logic still consumes any framed
body afterwards.

Note: code that referenced req.body or body-derived form fields inside
the pre-request handler will now see an empty body. Inspect headers,
path, query parameters, or matched_route instead.

Also document the handler execution order in README and update the
pre-request cookbook pages (en/ja).
This commit is contained in:
yhirose committed 2026-06-09 13:49:56 -04:00
1 parent 79d83feb18
commit 77bdf7921a
5 files changed
+133 -25

No files matched your search

+29 -17
View File
@@ -1812,8 +1812,8 @@ private:
const std::string &etag, time_t mtime) const;
bool check_if_range(Request &req, const std::string &etag,
time_t mtime) const;
bool dispatch_request(Request &req, Response &res,
const Handlers &handlers) const;
bool dispatch_request(Request &req, Response &res, const Handlers &handlers,
Stream &strm);
bool dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
const HandlersForContentReader &handlers) const;
@@ -11955,26 +11955,26 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
}
}
// Read content into `req.body`
if (!read_content(strm, req, res)) {
output_error_log(Error::Read, &req);
return false;
}
// NOTE: `req.body` is not read here. For a regular handler the body is
// read inside dispatch_request(), after the route has matched and the
// pre-request handler has approved the request, so that a rejected
// request (e.g. failed authentication) never forces us to buffer a
// potentially large body.
}
// Regular handler
if (req.method == "GET" || req.method == "HEAD") {
return dispatch_request(req, res, get_handlers_);
return dispatch_request(req, res, get_handlers_, strm);
} else if (req.method == "POST") {
return dispatch_request(req, res, post_handlers_);
return dispatch_request(req, res, post_handlers_, strm);
} else if (req.method == "PUT") {
return dispatch_request(req, res, put_handlers_);
return dispatch_request(req, res, put_handlers_, strm);
} else if (req.method == "DELETE") {
return dispatch_request(req, res, delete_handlers_);
return dispatch_request(req, res, delete_handlers_, strm);
} else if (req.method == "OPTIONS") {
return dispatch_request(req, res, options_handlers_);
return dispatch_request(req, res, options_handlers_, strm);
} else if (req.method == "PATCH") {
return dispatch_request(req, res, patch_handlers_);
return dispatch_request(req, res, patch_handlers_, strm);
}
res.status = StatusCode::BadRequest_400;
@@ -11982,17 +11982,29 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
}
inline bool Server::dispatch_request(Request &req, Response &res,
const Handlers &handlers) const {
const Handlers &handlers, Stream &strm) {
for (const auto &x : handlers) {
const auto &matcher = x.first;
const auto &handler = x.second;
if (matcher->match(req)) {
req.matched_route = matcher->pattern();
if (!pre_request_handler_ ||
pre_request_handler_(req, res) != HandlerResponse::Handled) {
handler(req, res);
// Run the pre-request handler before reading the body so a rejected
// request (e.g. failed authentication) never forces us to buffer a
// potentially large body. `req.matched_route` is available here.
if (pre_request_handler_ &&
pre_request_handler_(req, res) == HandlerResponse::Handled) {
return true;
}
// The route matched and the request was approved; read the body now.
if (detail::expect_content(req) && !read_content(strm, req, res)) {
output_error_log(Error::Read, &req);
return false;
}
handler(req, res);
return true;
}
}