diff --git a/httplib.h b/httplib.h index c01c852a..b7b2104e 100644 --- a/httplib.h +++ b/httplib.h @@ -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 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 diff --git a/test/test.cc b/test/test.cc index f2c8f921..061823b1 100644 --- a/test/test.cc +++ b/test/test.cc @@ -13535,6 +13535,63 @@ TEST(ClientPayloadTest, DoesNotReserveTheClaimedLength) { EXPECT_FALSE(cli.Get("/")); } +TEST(ExpectContinueTest, EarlyFinalResponseClosesConnection) { + std::atomic 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(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(&opt), +#else + &opt, +#endif + sizeof(opt)); + ::bind(srv, reinterpret_cast(&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(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.