Send 100 Continue only when the request body is read

The server wrote 100 Continue as soon as it saw the expectation, before
pre_routing_handler, pre_request_handler, or routing ran. A request
those handlers rejected, or one that matched no route, still invited
the client to send a body the server would never read.

Defer the interim response until the body is about to be read. If the
request is answered without reading the body, 100 Continue is never
sent and the connection is closed, since whether and when the client
sends the body is unknown.

Also treat a 417 returned by expect_100_continue_handler as the final
response. It used to be written as a bare status line, after which the
request was processed and a second response was written.
This commit is contained in:
yhirose
2026-09-19 17:28:09 -04:00
parent 2e5480ad65
commit 09c02f1335
3 changed files with 186 additions and 11 deletions
+8 -3
View File
@@ -564,14 +564,15 @@ svr.set_pre_request_handler([](const auto& req, auto& res) {
```
Request received
│
├─ expect_100_continue_handler (when the request has "Expect: 100-continue")
│ └─ returns a status other than 100 → stop here
│
├─ pre_routing_handler route not matched yet, body not read
│ └─ returns Handled → stop here
│
├─ file_request_handler (GET/HEAD, static file serving)
│
├─ expect_100_continue_handler (when the request has "Expect: 100-continue")
│
├─ route matching → req.matched_route is set
│
├─ pre_request_handler route matched, body NOT read yet
@@ -587,6 +588,8 @@ Request received
Use `pre_routing_handler` to reject a request as early as possible, before the route is known. Use `pre_request_handler` for route-specific checks, since `req.matched_route` is available and the body has not been read yet.
For a request with `Expect: 100-continue`, the `100 Continue` response is not sent until the body is about to be read. A request rejected before that point (by `pre_routing_handler`, `pre_request_handler`, or because no route matched) gets its final response without `100 Continue`, so the client never sends the body.
A WebSocket upgrade request that matches a route registered with `svr.WebSocket()` takes a shorter path: `pre_routing_handler`, then route matching (`req.matched_route` is set), then `pre_request_handler`, then the WebSocket handler. If either hook returns `Handled`, its response is sent as a regular HTTP response and the connection is not upgraded. Once the connection is upgraded, `post_routing_handler` does not run.
### Response user data
@@ -848,7 +851,9 @@ svr.Get("/content", [&](const Request &req, Response &res) {
### 'Expect: 100-continue' handler
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
By default, the server accepts an `Expect: 100-continue` header and sends a `100 Continue` response when it starts reading the request body. If the request is answered without reading the body, `100 Continue` is not sent and the connection is closed after the response.
The handler runs before `pre_routing_handler`. Returning `100` lets the request proceed; returning any other status sends that status as the final response and closes the connection.
```cpp
// Send a '417 Expectation Failed' response.