Skip to content

Commit dbd5ca4

Browse files
committed
Add error handling for stream read timeouts and connection closures
1 parent 143019a commit dbd5ca4

File tree

3 files changed

+324
-50
lines changed

3 files changed

+324
-50
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ httplib::SSLClient cli("localhost");
722722
Here is the list of errors from `Result::error()`.
723723
724724
```c++
725-
enum Error {
725+
enum class Error {
726726
Success = 0,
727727
Unknown,
728728
Connection,
@@ -739,6 +739,24 @@ enum Error {
739739
Compression,
740740
ConnectionTimeout,
741741
ProxyConnection,
742+
ConnectionClosed,
743+
Timeout,
744+
ResourceExhaustion,
745+
TooManyFormDataFiles,
746+
ExceedMaxPayloadSize,
747+
ExceedUriMaxLength,
748+
ExceedMaxSocketDescriptorCount,
749+
InvalidRequestLine,
750+
InvalidHTTPMethod,
751+
InvalidHTTPVersion,
752+
InvalidHeaders,
753+
MultipartParsing,
754+
OpenFile,
755+
Listen,
756+
GetSockName,
757+
UnsupportedAddressFamily,
758+
HTTPParsing,
759+
InvalidRangeHeader,
742760
};
743761
```
744762

httplib.h

Lines changed: 81 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,50 @@ struct Response {
838838
std::string file_content_content_type_;
839839
};
840840

841+
enum class Error {
842+
Success = 0,
843+
Unknown,
844+
Connection,
845+
BindIPAddress,
846+
Read,
847+
Write,
848+
ExceedRedirectCount,
849+
Canceled,
850+
SSLConnection,
851+
SSLLoadingCerts,
852+
SSLServerVerification,
853+
SSLServerHostnameVerification,
854+
UnsupportedMultipartBoundaryChars,
855+
Compression,
856+
ConnectionTimeout,
857+
ProxyConnection,
858+
ConnectionClosed,
859+
Timeout,
860+
ResourceExhaustion,
861+
TooManyFormDataFiles,
862+
ExceedMaxPayloadSize,
863+
ExceedUriMaxLength,
864+
ExceedMaxSocketDescriptorCount,
865+
InvalidRequestLine,
866+
InvalidHTTPMethod,
867+
InvalidHTTPVersion,
868+
InvalidHeaders,
869+
MultipartParsing,
870+
OpenFile,
871+
Listen,
872+
GetSockName,
873+
UnsupportedAddressFamily,
874+
HTTPParsing,
875+
InvalidRangeHeader,
876+
877+
// For internal use only
878+
SSLPeerCouldBeClosed_,
879+
};
880+
881+
std::string to_string(Error error);
882+
883+
std::ostream &operator<<(std::ostream &os, const Error &obj);
884+
841885
class Stream {
842886
public:
843887
virtual ~Stream() = default;
@@ -856,6 +900,11 @@ class Stream {
856900

857901
ssize_t write(const char *ptr);
858902
ssize_t write(const std::string &s);
903+
904+
Error get_error() const { return error_; }
905+
906+
protected:
907+
Error error_ = Error::Success;
859908
};
860909

861910
class TaskQueue {
@@ -1292,48 +1341,6 @@ class Server {
12921341
detail::write_headers;
12931342
};
12941343

1295-
enum class Error {
1296-
Success = 0,
1297-
Unknown,
1298-
Connection,
1299-
BindIPAddress,
1300-
Read,
1301-
Write,
1302-
ExceedRedirectCount,
1303-
Canceled,
1304-
SSLConnection,
1305-
SSLLoadingCerts,
1306-
SSLServerVerification,
1307-
SSLServerHostnameVerification,
1308-
UnsupportedMultipartBoundaryChars,
1309-
Compression,
1310-
ConnectionTimeout,
1311-
ProxyConnection,
1312-
ResourceExhaustion,
1313-
TooManyFormDataFiles,
1314-
ExceedMaxPayloadSize,
1315-
ExceedUriMaxLength,
1316-
ExceedMaxSocketDescriptorCount,
1317-
InvalidRequestLine,
1318-
InvalidHTTPMethod,
1319-
InvalidHTTPVersion,
1320-
InvalidHeaders,
1321-
MultipartParsing,
1322-
OpenFile,
1323-
Listen,
1324-
GetSockName,
1325-
UnsupportedAddressFamily,
1326-
HTTPParsing,
1327-
InvalidRangeHeader,
1328-
1329-
// For internal use only
1330-
SSLPeerCouldBeClosed_,
1331-
};
1332-
1333-
std::string to_string(Error error);
1334-
1335-
std::ostream &operator<<(std::ostream &os, const Error &obj);
1336-
13371344
class Result {
13381345
public:
13391346
Result() = default;
@@ -2437,6 +2444,8 @@ inline std::string to_string(const Error error) {
24372444
case Error::Compression: return "Compression failed";
24382445
case Error::ConnectionTimeout: return "Connection timed out";
24392446
case Error::ProxyConnection: return "Proxy connection failed";
2447+
case Error::ConnectionClosed: return "Connection closed by server";
2448+
case Error::Timeout: return "Read timeout";
24402449
case Error::ResourceExhaustion: return "Resource exhaustion";
24412450
case Error::TooManyFormDataFiles: return "Too many form data files";
24422451
case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
@@ -7273,13 +7282,15 @@ inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
72737282
auto n = stream->read(buf, to_read);
72747283

72757284
if (n < 0) {
7276-
last_error = Error::Read;
7285+
last_error = stream->get_error();
7286+
if (last_error == Error::Success) { last_error = Error::Read; }
72777287
eof = true;
72787288
return n;
72797289
}
72807290
if (n == 0) {
72817291
// Unexpected EOF before content_length
7282-
last_error = Error::Read;
7292+
last_error = stream->get_error();
7293+
if (last_error == Error::Success) { last_error = Error::Read; }
72837294
eof = true;
72847295
return 0;
72857296
}
@@ -7296,7 +7307,8 @@ inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
72967307
size_t chunk_total = 0;
72977308
auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
72987309
if (n < 0) {
7299-
last_error = Error::Read;
7310+
last_error = stream->get_error();
7311+
if (last_error == Error::Success) { last_error = Error::Read; }
73007312
eof = true;
73017313
return n;
73027314
}
@@ -7387,7 +7399,10 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
73877399
}
73887400
}
73897401

7390-
if (!wait_readable()) { return -1; }
7402+
if (!wait_readable()) {
7403+
error_ = Error::Timeout;
7404+
return -1;
7405+
}
73917406

73927407
read_buff_off_ = 0;
73937408
read_buff_content_size_ = 0;
@@ -7396,6 +7411,11 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
73967411
auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
73977412
CPPHTTPLIB_RECV_FLAGS);
73987413
if (n <= 0) {
7414+
if (n == 0) {
7415+
error_ = Error::ConnectionClosed;
7416+
} else {
7417+
error_ = Error::Read;
7418+
}
73997419
return n;
74007420
} else if (n <= static_cast<ssize_t>(size)) {
74017421
memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
@@ -7407,7 +7427,15 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
74077427
return static_cast<ssize_t>(size);
74087428
}
74097429
} else {
7410-
return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
7430+
auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
7431+
if (n <= 0) {
7432+
if (n == 0) {
7433+
error_ = Error::ConnectionClosed;
7434+
} else {
7435+
error_ = Error::Read;
7436+
}
7437+
}
7438+
return n;
74117439
}
74127440
}
74137441

@@ -11435,7 +11463,9 @@ inline bool SSLSocketStream::wait_writable() const {
1143511463

1143611464
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
1143711465
if (SSL_pending(ssl_) > 0) {
11438-
return SSL_read(ssl_, ptr, static_cast<int>(size));
11466+
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
11467+
if (ret == 0) { error_ = Error::ConnectionClosed; }
11468+
return ret;
1143911469
} else if (wait_readable()) {
1144011470
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
1144111471
if (ret < 0) {
@@ -11460,9 +11490,12 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
1146011490
}
1146111491
}
1146211492
assert(ret < 0);
11493+
} else if (ret == 0) {
11494+
error_ = Error::ConnectionClosed;
1146311495
}
1146411496
return ret;
1146511497
} else {
11498+
error_ = Error::Timeout;
1146611499
return -1;
1146711500
}
1146811501
}

0 commit comments

Comments
 (0)