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 committed 2026-09-19 17:28:09 -04:00
1 parent 2e5480ad65
commit 09c02f1335
3 files changed
+186 -11

No files matched your search

+20 -8
View File
@@ -1756,6 +1756,7 @@ struct Request {
// private members...
bool body_consumed_ = false;
bool expect_100_continue_pending_ = false;
size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
size_t content_length_ = 0;
ContentProvider content_provider_;
@@ -13298,7 +13299,10 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
// Prepare additional headers
if (close_connection ||
detail::has_header_token(req.headers, "Connection", "close") ||
400 <= res.status) { // Don't leave connections open after errors
400 <= res.status || // Don't leave connections open after errors
// The client withholds the body until `100 Continue`, which was never
// sent, so whether and when the body follows is unknown.
(req.expect_100_continue_pending_ && detail::has_framed_body(req))) {
res.set_header("Connection", "close");
} else {
std::string s = "timeout=";
@@ -13549,6 +13553,13 @@ inline bool Server::read_content_core(
}
#endif
// The client is waiting for this before it sends the body.
if (req.expect_100_continue_pending_) {
req.expect_100_continue_pending_ = false;
detail::write_response_line(strm, StatusCode::Continue_100);
strm.write("\r\n");
}
if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
out, true)) {
return false;
@@ -14386,19 +14397,20 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
// case-insensitive, and a 100-continue expectation in an HTTP/1.0 request
// must be ignored. An expectation we do not recognize is left alone; the
// 417 the section allows for one is a MAY, not a requirement.
//
// `100 Continue` itself is deferred until the body is actually read (see
// read_content_core), so a request rejected by a later handler never
// invites the client to send a body nobody will read.
if (req.version != "HTTP/1.0" &&
detail::has_header_token(req.headers, "Expect", "100-continue")) {
int status = StatusCode::Continue_100;
if (expect_100_continue_handler_) {
status = expect_100_continue_handler_(req, res);
}
switch (status) {
case StatusCode::Continue_100:
case StatusCode::ExpectationFailed_417:
detail::write_response_line(strm, status);
strm.write("\r\n");
break;
default:
if (status == StatusCode::Continue_100) {
req.expect_100_continue_pending_ = true;
} else {
if (res.status == -1) { res.status = status; }
connection_closed = true;
return write_response(strm, true, req, res);
}