From 45820de3327cd54d23675b7a6b1343d07ffdf837 Mon Sep 17 00:00:00 2001 From: yhirose Date: Wed, 18 Mar 2026 18:29:19 -0400 Subject: [PATCH] Enhance stream handling in LongPollingTest and add new test for client close detection --- httplib.h | 4 ++-- test/test.cc | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/httplib.h b/httplib.h index 997a8f8d..24239d82 100644 --- a/httplib.h +++ b/httplib.h @@ -1005,8 +1005,8 @@ private: protected: std::streamsize xsputn(const char *s, std::streamsize n) override { - sink_.write(s, static_cast(n)); - return n; + if (sink_.write(s, static_cast(n))) { return n; } + return 0; } private: diff --git a/test/test.cc b/test/test.cc index e7fcac8a..e76abfc6 100644 --- a/test/test.cc +++ b/test/test.cc @@ -61,6 +61,8 @@ static int get_base_port() { return shard ? 11234 + std::atoi(shard) * 100 : 1234; } +// NOTE: PORT is only for legacy fixtures (ServerTest, etc.). +// New standalone tests MUST use svr.bind_to_any_port() instead. const int PORT = get_base_port(); const string LONG_QUERY_VALUE = string(25000, '@'); @@ -8149,6 +8151,56 @@ TEST(LongPollingTest, ClientCloseDetection) { ASSERT_FALSE(res); } +TEST(LongPollingTest, ClientCloseDetectionWithStreamOperator) { + Server svr; + + svr.Get("/events", [&](const Request & /*req*/, Response &res) { + res.set_chunked_content_provider( + "text/plain", [](std::size_t const, DataSink &sink) -> bool { + EXPECT_TRUE(sink.is_writable()); // the socket is alive + sink.os << "hello"; + EXPECT_TRUE(sink.os.good()); + + // Wait for the client to close the connection + auto count = 10; + while (count > 0 && sink.is_writable()) { + this_thread::sleep_for(chrono::milliseconds(10)); + count--; + } + + // After client disconnect, write repeatedly until the socket + // write actually fails (small writes may be absorbed by the + // kernel buffer) + std::string chunk(1024, 'x'); + for (int i = 0; i < 1000 && sink.os.good(); i++) { + sink.os << chunk; + } + EXPECT_TRUE(sink.os.fail()); + + return true; + }); + }); + + auto port = svr.bind_to_any_port("localhost"); + auto listen_thread = std::thread([&svr]() { svr.listen_after_bind(); }); + auto se = detail::scope_exit([&] { + svr.stop(); + listen_thread.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + Client cli("localhost", port); + + auto res = cli.Get("/events", [&](const char *data, size_t data_length) { + EXPECT_EQ("hello", string(data, data_length)); + return false; // close the socket immediately. + }); + + ASSERT_FALSE(res); +} + TEST(GetWithParametersTest, GetWithParameters) { Server svr;