mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Close the connection after an early answer to Expect: 100-continue
When the final response came before 100 Continue, the body was never sent but the connection was kept, so the server could read the next request as that body. Close it after the response.
This commit is contained in:
1 parent
aaccde2122
commit
5d10305218
2 files changed
+63
-1
No files matched your search
@@ -16596,8 +16596,13 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
output_error_log(error, &req);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// A final response came first, so the body this request declared was
|
||||
// never sent, and the server may still be waiting for it. The
|
||||
// connection cannot carry another request.
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
socket_should_be_closed_when_request_is_done_ = true;
|
||||
}
|
||||
// If not 100 Continue, server returned an error; proceed with that response
|
||||
}
|
||||
|
||||
// Body
|
||||
|
||||
@@ -13535,6 +13535,63 @@ TEST(ClientPayloadTest, DoesNotReserveTheClaimedLength) {
|
||||
EXPECT_FALSE(cli.Get("/"));
|
||||
}
|
||||
|
||||
TEST(ExpectContinueTest, EarlyFinalResponseClosesConnection) {
|
||||
std::atomic<bool> closed_by_client{false};
|
||||
auto server_thread = std::thread([&] {
|
||||
auto srv = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
default_socket_options(srv);
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(PORT));
|
||||
::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
||||
int opt = 1;
|
||||
::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR,
|
||||
#ifdef _WIN32
|
||||
reinterpret_cast<const char *>(&opt),
|
||||
#else
|
||||
&opt,
|
||||
#endif
|
||||
sizeof(opt));
|
||||
::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
|
||||
::listen(srv, 1);
|
||||
|
||||
auto cli = ::accept(srv, nullptr, nullptr);
|
||||
if (cli != INVALID_SOCKET) {
|
||||
detail::set_socket_opt_time(cli, SOL_SOCKET, SO_RCVTIMEO, 3, 0);
|
||||
|
||||
// Answer the head without asking for the body, and keep the connection.
|
||||
std::string head;
|
||||
char c;
|
||||
while (head.find("\r\n\r\n") == std::string::npos &&
|
||||
::recv(cli, &c, 1, 0) == 1) {
|
||||
head += c;
|
||||
}
|
||||
std::string res =
|
||||
"HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n";
|
||||
::send(cli, res.data(), static_cast<int>(res.size()), 0);
|
||||
|
||||
// Bytes here would be read as the body the first request declared.
|
||||
char buf[256];
|
||||
closed_by_client = ::recv(cli, buf, sizeof(buf), 0) == 0;
|
||||
detail::close_socket(cli);
|
||||
}
|
||||
detail::close_socket(srv);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
Client cli("127.0.0.1", PORT);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1, 0);
|
||||
// Long enough for the client to send Expect: 100-continue.
|
||||
auto res = cli.Post("/upload", std::string(2048, 'B'), "text/plain");
|
||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
cli.Get("/second");
|
||||
|
||||
server_thread.join();
|
||||
EXPECT_TRUE(closed_by_client);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
// `payload_max_length` is not enforced on decompressed body in ContentReader
|
||||
// path.
|
||||
|
||||
Reference in new issue
Block a user