mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-01-19 04:52:08 +00:00
* Resolve #2262 * Enhance request handling on Windows by adding early response check for large request bodies * Enhance early response handling for large requests with long URIs on Windows
This commit is contained in:
46
httplib.h
46
httplib.h
@@ -286,6 +286,7 @@ using socket_t = int;
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
@@ -11014,6 +11015,44 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
}
|
||||
}
|
||||
|
||||
// After sending request line and headers, wait briefly for an early server
|
||||
// response (e.g. 4xx) and avoid sending a potentially large request body
|
||||
// unnecessarily. This workaround is only enabled on Windows because Unix
|
||||
// platforms surface write errors (EPIPE) earlier; on Windows kernel send
|
||||
// buffering can accept large writes even when the peer already responded.
|
||||
// Check the stream first (which covers SSL via `is_readable()`), then
|
||||
// fall back to select on the socket. Only perform the wait for very large
|
||||
// request bodies to avoid interfering with normal small requests and
|
||||
// reduce side-effects. Poll briefly (up to 50ms) for an early response.
|
||||
#if defined(_WIN32)
|
||||
if (req.body.size() > (1u << 20) &&
|
||||
req.path.size() >
|
||||
CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { // > 1MB && long URI
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
const auto max_wait_ms = 50;
|
||||
for (;;) {
|
||||
auto sock = strm.socket();
|
||||
// Prefer socket-level readiness to avoid SSL_pending() false-positives
|
||||
// from SSL internals. If the underlying socket is readable, assume an
|
||||
// early response may be present.
|
||||
if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
|
||||
return false;
|
||||
}
|
||||
// Fallback to stream-level check for non-socket streams or when the
|
||||
// socket isn't reporting readable. Avoid using `is_readable()` for
|
||||
// SSL, since `SSL_pending()` may report buffered records that do not
|
||||
// indicate a complete application-level response yet.
|
||||
if (!is_ssl() && strm.is_readable()) { return false; }
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
|
||||
.count();
|
||||
if (elapsed >= max_wait_ms) { break; }
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Body
|
||||
if (req.body.empty()) {
|
||||
return write_content_with_provider(strm, req, error);
|
||||
@@ -11191,7 +11230,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
Response &res, bool close_connection,
|
||||
Error &error) {
|
||||
// Send request
|
||||
if (!write_request(strm, req, close_connection, error)) { return false; }
|
||||
auto write_request_success =
|
||||
write_request(strm, req, close_connection, error);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl()) {
|
||||
@@ -11209,11 +11249,13 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// Receive response and headers
|
||||
if (!read_response_line(strm, req, res) ||
|
||||
!detail::read_headers(strm, res.headers)) {
|
||||
error = Error::Read;
|
||||
if (write_request_success) { error = Error::Read; }
|
||||
output_error_log(error, &req);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!write_request_success) { return false; }
|
||||
|
||||
// Body
|
||||
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
|
||||
req.method != "CONNECT") {
|
||||
|
||||
47
test/test.cc
47
test/test.cc
@@ -6567,6 +6567,53 @@ TEST_F(ServerTest, PreCompressionLoggingOnlyPreLogger) {
|
||||
EXPECT_EQ(test_content, pre_compression_body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, SendLargeBodyAfterRequestLineError) {
|
||||
{
|
||||
Request req;
|
||||
req.method = "POST";
|
||||
req.path = "/post-large?q=" + LONG_QUERY_VALUE;
|
||||
req.body = LARGE_DATA;
|
||||
|
||||
Response res;
|
||||
auto error = Error::Success;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
cli_.set_keep_alive(true);
|
||||
auto ret = cli_.send(req, res, error);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
|
||||
.count();
|
||||
|
||||
EXPECT_FALSE(ret);
|
||||
EXPECT_EQ(StatusCode::UriTooLong_414, res.status);
|
||||
EXPECT_EQ("close", res.get_header_value("Connection"));
|
||||
EXPECT_FALSE(cli_.is_socket_open());
|
||||
EXPECT_LE(elapsed, 200);
|
||||
}
|
||||
|
||||
{
|
||||
// Send an extra GET request to ensure error recovery without hanging
|
||||
Request req;
|
||||
req.method = "GET";
|
||||
req.path = "/hi";
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto res = cli_.send(req);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
|
||||
.count();
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
EXPECT_LE(elapsed, 100);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ZstdDecompressor, ChunkedDecompression) {
|
||||
std::string data;
|
||||
for (size_t i = 0; i < 32 * 1024; ++i) {
|
||||
|
||||
Reference in New Issue
Block a user