Browse Source

Report connection timeout as separate event (#1171)

Gregor Jasny 3 years ago
parent
commit
87e03dd1ce
2 changed files with 33 additions and 12 deletions
  1. 27 11
      httplib.h
  2. 6 1
      test/test.cc

+ 27 - 11
httplib.h

@@ -799,6 +799,7 @@ enum class Error {
   SSLServerVerification,
   SSLServerVerification,
   UnsupportedMultipartBoundaryChars,
   UnsupportedMultipartBoundaryChars,
   Compression,
   Compression,
+  ConnectionTimeout,
 };
 };
 
 
 std::string to_string(const Error error);
 std::string to_string(const Error error);
@@ -1594,6 +1595,7 @@ inline std::string to_string(const Error error) {
   case Error::UnsupportedMultipartBoundaryChars:
   case Error::UnsupportedMultipartBoundaryChars:
     return "UnsupportedMultipartBoundaryChars";
     return "UnsupportedMultipartBoundaryChars";
   case Error::Compression: return "Compression";
   case Error::Compression: return "Compression";
+  case Error::ConnectionTimeout: return "ConnectionTimeout";
   case Error::Unknown: return "Unknown";
   case Error::Unknown: return "Unknown";
   default: break;
   default: break;
   }
   }
@@ -2313,7 +2315,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
 #endif
 #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
 #ifdef CPPHTTPLIB_USE_POLL
   struct pollfd pfd_read;
   struct pollfd pfd_read;
   pfd_read.fd = sock;
   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); });
   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)) {
   if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
     int error = 0;
     int error = 0;
     socklen_t len = sizeof(error);
     socklen_t len = sizeof(error);
     auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
     auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
                           reinterpret_cast<char *>(&error), &len);
                           reinterpret_cast<char *>(&error), &len);
-    return res >= 0 && !error;
+    auto successful = res >= 0 && !error;
+    return successful ? Error::Success : Error::Connection;
   }
   }
-  return false;
+
+  return Error::Connection;
 #else
 #else
 #ifndef _WIN32
 #ifndef _WIN32
-  if (sock >= FD_SETSIZE) { return false; }
+  if (sock >= FD_SETSIZE) { return Error::Connection; }
 #endif
 #endif
 
 
   fd_set fdsr;
   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<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
     return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
   });
   });
 
 
+  if (ret == 0) {
+    return Error::ConnectionTimeout;
+  }
+
   if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
   if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
     int error = 0;
     int error = 0;
     socklen_t len = sizeof(error);
     socklen_t len = sizeof(error);
-    return getsockopt(sock, SOL_SOCKET, SO_ERROR,
-                      reinterpret_cast<char *>(&error), &len) >= 0 &&
-           !error;
+    auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+                          reinterpret_cast<char *>(&error), &len);
+    auto successful = res >= 0 && !error;
+    return successful ? Error::Success : Error::Connection;
   }
   }
-  return false;
+  return Error::Connection;
 #endif
 #endif
 }
 }
 
 
@@ -2684,12 +2697,15 @@ inline socket_t create_client_socket(
             ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
             ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
 
 
         if (ret < 0) {
         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;
             error = Error::Connection;
             return false;
             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);
         set_nonblocking(sock2, false);

+ 6 - 1
test/test.cc

@@ -622,9 +622,14 @@ TEST(ConnectionErrorTest, Timeout) {
 #endif
 #endif
   cli.set_connection_timeout(std::chrono::seconds(2));
   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("/");
   auto res = cli.Get("/");
   ASSERT_TRUE(!res);
   ASSERT_TRUE(!res);
-  EXPECT_TRUE(res.error() == Error::Connection);
+  EXPECT_EQ(Error::ConnectionTimeout, res.error());
 }
 }
 
 
 TEST(CancelTest, NoCancel_Online) {
 TEST(CancelTest, NoCancel_Online) {