diff --git a/httplib.h b/httplib.h index 38c88ab..2d8548c 100644 --- a/httplib.h +++ b/httplib.h @@ -799,6 +799,7 @@ enum class Error { SSLServerVerification, UnsupportedMultipartBoundaryChars, Compression, + ConnectionTimeout, }; std::string to_string(const Error error); @@ -1594,6 +1595,7 @@ inline std::string to_string(const Error error) { case Error::UnsupportedMultipartBoundaryChars: return "UnsupportedMultipartBoundaryChars"; case Error::Compression: return "Compression"; + case Error::ConnectionTimeout: return "ConnectionTimeout"; case Error::Unknown: return "Unknown"; default: break; } @@ -2313,7 +2315,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { #endif } -inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; @@ -2323,17 +2325,23 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + if (poll_res == 0) { + return Error::ConnectionTimeout; + } + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); - return res >= 0 && !error; + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; } - return false; + + return Error::Connection; #else #ifndef _WIN32 - if (sock >= FD_SETSIZE) { return false; } + if (sock >= FD_SETSIZE) { return Error::Connection; } #endif fd_set fdsr; @@ -2351,14 +2359,19 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); }); + if (ret == 0) { + return Error::ConnectionTimeout; + } + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { int error = 0; socklen_t len = sizeof(error); - return getsockopt(sock, SOL_SOCKET, SO_ERROR, - reinterpret_cast(&error), &len) >= 0 && - !error; + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; } - return false; + return Error::Connection; #endif } @@ -2684,12 +2697,15 @@ inline socket_t create_client_socket( ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); if (ret < 0) { - if (is_connection_error() || - !wait_until_socket_is_ready(sock2, connection_timeout_sec, - connection_timeout_usec)) { + if (is_connection_error()) { error = Error::Connection; return false; } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { + return false; + } } set_nonblocking(sock2, false); diff --git a/test/test.cc b/test/test.cc index bf5687f..69f701b 100644 --- a/test/test.cc +++ b/test/test.cc @@ -622,9 +622,14 @@ TEST(ConnectionErrorTest, Timeout) { #endif cli.set_connection_timeout(std::chrono::seconds(2)); + // only probe one address type so that the error reason + // correlates to the timed-out IPv4, not the unsupported + // IPv6 connection attempt + cli.set_address_family(AF_INET); + auto res = cli.Get("/"); ASSERT_TRUE(!res); - EXPECT_TRUE(res.error() == Error::Connection); + EXPECT_EQ(Error::ConnectionTimeout, res.error()); } TEST(CancelTest, NoCancel_Online) {