Merge pull request #2504 from emreay-/skip-drain-on-closing-connection

Skip request body drain when connection will close
This commit is contained in:
yhirose
2026-07-22 12:01:20 -04:00
committed by GitHub
2 changed files with 60 additions and 5 deletions
+11 -5
View File
@@ -12772,13 +12772,19 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
// Drain any unconsumed framed body to prevent request smuggling on
// keep-alive. Without framing there is no body to drain — reading would
// consume the next request (issue #2450).
// consume the next request (issue #2450). If the response has committed the
// connection to close, there is no next request to protect.
if (!req.body_consumed_ && detail::has_framed_body(req)) {
int dummy_status;
if (!detail::read_content(
strm, req, payload_max_length_, dummy_status, nullptr,
[](const char *, size_t, size_t, size_t) { return true; }, false)) {
if (res.get_header_value("Connection") == "close") {
connection_closed = true;
} else {
int dummy_status;
if (!detail::read_content(
strm, req, payload_max_length_, dummy_status, nullptr,
[](const char *, size_t, size_t, size_t) { return true; },
false)) {
connection_closed = true;
}
}
}
+49
View File
@@ -20039,6 +20039,55 @@ TEST(KeepAliveTest, DeleteWithoutContentLengthDoesNotEatNextRequest) {
EXPECT_EQ(2, delete_count.load());
}
TEST(KeepAliveTest, UnconsumedChunkedBodyIsNotDrainedWhenResponseCloses) {
Server svr;
svr.Post("/ingest", [&](const Request &, Response &res,
const ContentReader &content_reader) {
auto consumed = content_reader([](const char *, size_t) { return false; });
EXPECT_FALSE(consumed);
res.status = StatusCode::Conflict_409;
res.set_header("Connection", "close");
});
auto port = svr.bind_to_any_port(HOST);
thread t = thread([&] { svr.listen_after_bind(); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
});
svr.wait_until_ready();
auto error = Error::Success;
auto sock = detail::create_client_socket(
HOST, "", port, AF_UNSPEC, false, false, nullptr,
/*connection_timeout_sec=*/2, 0,
/*read_timeout_sec=*/1, 0,
/*write_timeout_sec=*/2, 0, std::string(), error);
ASSERT_NE(INVALID_SOCKET, sock);
auto sock_se = detail::scope_exit([&] { detail::close_socket(sock); });
std::string request = "POST /ingest HTTP/1.1\r\n"
"Host: localhost\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"4\r\n"
"data\r\n";
auto sent = send(sock, request.data(), request.size(), 0);
ASSERT_EQ(static_cast<ssize_t>(request.size()), sent);
std::string response;
ssize_t received = 0;
do {
char buf[4096];
received = recv(sock, buf, sizeof(buf), 0);
if (received > 0) { response.append(buf, static_cast<size_t>(received)); }
} while (received > 0);
EXPECT_NE(std::string::npos, response.find("HTTP/1.1 409 Conflict"));
EXPECT_NE(std::string::npos, response.find("Connection: close"));
EXPECT_EQ(0, received);
}
namespace no_proxy_test {
// Server bound to 127.0.0.1:<dynamic>, listen thread spawned by listen(),