Skip to content

Commit 22f9b2a

Browse files
committed
More robust error handling
- Check if socket is alive before waiting to write to it to prevent SIGPIPE - Consume SIGPIPE on client - Read response line and headers after bad request
1 parent 4b2b851 commit 22f9b2a

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

httplib.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7133,7 +7133,7 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
71337133
}
71347134

71357135
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
7136-
if (!wait_writable()) { return -1; }
7136+
if (!detail::is_socket_alive(sock_) || !wait_writable()) { return -1; }
71377137

71387138
#if defined(_WIN32) && !defined(_WIN64)
71397139
size =
@@ -8618,7 +8618,11 @@ inline ClientImpl::ClientImpl(const std::string &host, int port,
86188618
const std::string &client_key_path)
86198619
: host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
86208620
host_and_port_(detail::make_host_and_port_string(host_, port, is_ssl())),
8621-
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
8621+
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {
8622+
#ifndef _WIN32
8623+
signal(SIGPIPE, SIG_IGN);
8624+
#endif
8625+
}
86228626

86238627
inline ClientImpl::~ClientImpl() {
86248628
// Wait until all the requests in flight are handled.
@@ -9516,7 +9520,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
95169520
Response &res, bool close_connection,
95179521
Error &error) {
95189522
// Send request
9519-
if (!write_request(strm, req, close_connection, error)) { return false; }
9523+
bool wrote = write_request(strm, req, close_connection, error);
95209524

95219525
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
95229526
if (is_ssl()) {
@@ -9539,6 +9543,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
95399543
return false;
95409544
}
95419545

9546+
if (!wrote) return false;
9547+
95429548
// Body
95439549
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
95449550
req.method != "CONNECT") {

test/test.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5898,6 +5898,48 @@ TEST_F(ServerTest, BadRequestLineCancelsKeepAlive) {
58985898
EXPECT_FALSE(cli_.is_socket_open());
58995899
}
59005900

5901+
TEST_F(ServerTest, SendLargeBodyAfterRequestLineError) {
5902+
Request post;
5903+
post.method = "POST";
5904+
post.path = "/post-large?q=" + LONG_QUERY_VALUE;
5905+
post.body = LARGE_DATA;
5906+
5907+
auto start = std::chrono::high_resolution_clock::now();
5908+
5909+
auto resPost = std::make_shared<Response>();
5910+
auto error = Error::Success;
5911+
cli_.set_keep_alive(true);
5912+
auto ret = cli_.send(post, *resPost, error);
5913+
5914+
auto end = std::chrono::high_resolution_clock::now();
5915+
auto elapsed =
5916+
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
5917+
.count();
5918+
5919+
EXPECT_FALSE(ret);
5920+
EXPECT_EQ(StatusCode::UriTooLong_414, resPost->status);
5921+
EXPECT_EQ("close", resPost->get_header_value("Connection"));
5922+
EXPECT_FALSE(cli_.is_socket_open());
5923+
EXPECT_LE(elapsed, 200);
5924+
5925+
// Send an extra GET request to ensure error recovery without hanging
5926+
Request get;
5927+
get.method = "GET";
5928+
get.path = "/hi";
5929+
5930+
start = std::chrono::high_resolution_clock::now();
5931+
auto resGet = cli_.send(get);
5932+
end = std::chrono::high_resolution_clock::now();
5933+
elapsed =
5934+
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
5935+
.count();
5936+
5937+
ASSERT_TRUE(resGet);
5938+
EXPECT_EQ(StatusCode::OK_200, resGet->status);
5939+
EXPECT_EQ("Hello World!", resGet->body);
5940+
EXPECT_LE(elapsed, 100);
5941+
}
5942+
59015943
TEST_F(ServerTest, StartTime) { auto res = cli_.Get("/test-start-time"); }
59025944

59035945
#ifdef CPPHTTPLIB_ZLIB_SUPPORT

0 commit comments

Comments
 (0)