]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: Added connect and request timeout settings.
authorTimo Sirainen <tss@iki.fi>
Wed, 5 Jun 2013 13:19:38 +0000 (16:19 +0300)
committerTimo Sirainen <tss@iki.fi>
Wed, 5 Jun 2013 13:19:38 +0000 (16:19 +0300)
src/lib-http/http-client-connection.c
src/lib-http/http-client-private.h
src/lib-http/http-client.c
src/lib-http/http-client.h

index 127eb4070da9584260d1771f31a99fff4faaa1c1..f40b1cd5584487dadd7932cd0c4b8c0acd16eab8 100644 (file)
@@ -8,6 +8,7 @@
 #include "ioloop.h"
 #include "istream.h"
 #include "ostream.h"
+#include "time-util.h"
 #include "iostream-rawlog.h"
 #include "iostream-ssl.h"
 #include "http-response-parser.h"
@@ -201,6 +202,17 @@ http_client_connection_check_idle(struct http_client_connection *conn)
        }
 }
 
+static void
+http_client_connection_request_timeout(struct http_client_connection *conn)
+{
+       unsigned int msecs = conn->client->set.request_timeout_msecs;
+
+       http_client_connection_abort_temp_error(&conn,
+               HTTP_CLIENT_REQUEST_ERROR_TIMED_OUT, t_strdup_printf(
+               "No response for request in %u.%03u secs",
+               msecs/1000, msecs%1000));
+}
+
 static void
 http_client_connection_continue_timeout(struct http_client_connection *conn)
 {
@@ -249,6 +261,11 @@ bool http_client_connection_next_request(struct http_client_connection *conn)
        if (conn->to_idle != NULL)
                timeout_remove(&conn->to_idle);
 
+       if (conn->client->set.request_timeout_msecs > 0 &&
+           conn->to_requests == NULL) {
+               conn->to_requests = timeout_add(conn->client->set.request_timeout_msecs,
+                                               http_client_connection_request_timeout, conn);
+       }
        req->conn = conn;
        conn->payload_continue = FALSE;
        if (conn->peer->no_payload_sync)
@@ -293,14 +310,26 @@ static void http_client_connection_destroy(struct connection *_conn)
        struct http_client_connection *conn =
                (struct http_client_connection *)_conn;
        const char *error;
+       unsigned int msecs;
 
        conn->closing = TRUE;
        conn->connected = FALSE;
 
        switch (_conn->disconnect_reason) {
        case CONNECTION_DISCONNECT_CONNECT_TIMEOUT:
-               http_client_peer_connection_failure(conn->peer, t_strdup_printf(
-                       "connect(%s) failed: Connection timed out", _conn->name));
+               if (conn->connected_timestamp.tv_sec == 0) {
+                       msecs = timeval_diff_msecs(&ioloop_timeval,
+                                                  &conn->connect_start_timestamp);
+                       http_client_peer_connection_failure(conn->peer, t_strdup_printf(
+                               "connect(%s) failed: Connection timed out in %u.%03u secs",
+                               _conn->name, msecs/1000, msecs%1000));
+               } else {
+                       msecs = timeval_diff_msecs(&ioloop_timeval,
+                                                  &conn->connected_timestamp);
+                       http_client_peer_connection_failure(conn->peer, t_strdup_printf(
+                               "SSL handshaking to %s failed: Connection timed out in %u.%03u secs",
+                               _conn->name, msecs/1000, msecs%1000));
+               }
                break;
        case CONNECTION_DISCONNECT_CONN_CLOSED:
                /* retry pending requests if possible */
@@ -453,6 +482,8 @@ static void http_client_connection_input(struct connection *_conn)
                http_client_payload_finished(conn);
                finished++;
        }
+       if (conn->to_requests != NULL)
+               timeout_reset(conn->to_requests);
 
        /* get first waiting request */
        if (array_count(&conn->request_wait_list) > 0) {
@@ -560,6 +591,9 @@ static void http_client_connection_input(struct connection *_conn)
                        req = req_idx[0];
                        no_payload = (strcmp(req->method, "HEAD") == 0);
                } else {
+                       /* no more requests waiting for the connection */
+                       if (conn->to_requests != NULL)
+                               timeout_remove(&conn->to_requests);
                        req = NULL;
                        no_payload = FALSE;
                }
@@ -597,6 +631,9 @@ static int http_client_connection_output(struct http_client_connection *conn)
        const char *error;
        int ret;
 
+       if (conn->to_requests != NULL)
+               timeout_reset(conn->to_requests);
+
        if ((ret = o_stream_flush(output)) <= 0) {
                if (ret < 0) {
                        http_client_connection_abort_temp_error(&conn,
@@ -635,6 +672,8 @@ http_client_connection_ready(struct http_client_connection *conn)
 
        conn->connected = TRUE;
        conn->peer->last_connect_failed = FALSE;
+       if (conn->to_connect != NULL)
+               timeout_remove(&conn->to_connect);
 
        if (conn->client->set.rawlog_dir != NULL &&
                stat(conn->client->set.rawlog_dir, &st) == 0) {
@@ -718,6 +757,7 @@ http_client_connection_connected(struct connection *_conn, bool success)
                http_client_peer_connection_failure(conn->peer, t_strdup_printf(
                        "connect(%s) failed: %m", _conn->name));
        } else {
+               conn->connected_timestamp = ioloop_timeval;
                http_client_connection_debug(conn, "Connected");
                if (conn->peer->addr.https_name != NULL) {
                        if (http_client_connection_ssl_init(conn, &error) < 0) {
@@ -758,12 +798,32 @@ http_client_connection_delayed_connect_error(struct http_client_connection *conn
        http_client_connection_unref(&conn);
 }
 
+static void http_client_connect_timeout(struct http_client_connection *conn)
+{
+       conn->conn.disconnect_reason = CONNECTION_DISCONNECT_CONNECT_TIMEOUT;
+       http_client_connection_destroy(&conn->conn);
+}
+
 static void http_client_connection_connect(struct http_client_connection *conn)
 {
+       unsigned int msecs;
+
+       conn->connect_start_timestamp = ioloop_timeval;
        if (connection_client_connect(&conn->conn) < 0) {
                conn->connect_errno = errno;
                conn->to_input = timeout_add_short(0,
                        http_client_connection_delayed_connect_error, conn);
+               return;
+       }
+
+       /* don't use connection.h timeout because we want this timeout
+          to include also the SSL handshake */
+       msecs = conn->client->set.connect_timeout_msecs;
+       if (msecs == 0)
+               msecs = conn->client->set.request_timeout_msecs;
+       if (msecs > 0) {
+               conn->to_connect =
+                       timeout_add(msecs, http_client_connect_timeout, conn);
        }
 }
 
@@ -831,6 +891,10 @@ void http_client_connection_unref(struct http_client_connection **_conn)
                ssl_iostream_unref(&conn->ssl_iostream);
        connection_deinit(&conn->conn);
 
+       if (conn->to_requests != NULL)
+               timeout_remove(&conn->to_requests);
+       if (conn->to_connect != NULL)
+               timeout_remove(&conn->to_connect);
        if (conn->to_input != NULL)
                timeout_remove(&conn->to_input);
        if (conn->to_idle != NULL)
@@ -855,6 +919,10 @@ void http_client_connection_unref(struct http_client_connection **_conn)
 
 void http_client_connection_switch_ioloop(struct http_client_connection *conn)
 {
+       if (conn->to_requests != NULL)
+               conn->to_requests = io_loop_move_timeout(&conn->to_requests);
+       if (conn->to_connect != NULL)
+               conn->to_requests = io_loop_move_timeout(&conn->to_connect);
        if (conn->to_input != NULL)
                conn->to_input = io_loop_move_timeout(&conn->to_input);
        if (conn->to_idle != NULL)
index 4d7aa6459e447389a8b242843511f3f7f48ae26c..023f8e9a1bd2a270dd138e5aaa3f2079bbb2cbc3 100644 (file)
@@ -134,10 +134,13 @@ struct http_client_connection {
 
        unsigned int id; // DEBUG: identify parallel connections
        int connect_errno;
+       struct timeval connect_start_timestamp;
+       struct timeval connected_timestamp;
 
        struct ssl_iostream *ssl_iostream;
        struct http_response_parser *http_parser;
-       struct timeout *to_input, *to_idle, *to_response;
+       struct timeout *to_connect, *to_input, *to_idle, *to_response;
+       struct timeout *to_requests;
 
        struct http_client_request *pending_request;
        struct istream *incoming_payload;
index f153b514323c660b4a20d67a381a18e55b222ec5..aa948531bdfd8da8cd86059bcd52cb9acd9b7baa 100644 (file)
@@ -96,6 +96,8 @@ struct http_client *http_client_init(const struct http_client_settings *set)
                (set->max_pipelined_requests > 0 ? set->max_pipelined_requests : 1);
        client->set.max_attempts = set->max_attempts;
        client->set.max_redirects = set->max_redirects;
+       client->set.request_timeout_msecs = set->request_timeout_msecs;
+       client->set.connect_timeout_msecs = set->connect_timeout_msecs;
        client->set.debug = set->debug;
 
        client->conn_list = http_client_connection_list_init();
index 9c8d1319041f771ee9f29d119ed9c43ac60d59a4..8a2c31ecf511a6978db05f9cf6abb01c215f575b 100644 (file)
@@ -57,6 +57,13 @@ struct http_client_settings {
        /* maximum number of attempts for a request */
        unsigned int max_attempts;
 
+       /* max time to wait for HTTP request to finish before retrying
+          (default = unlimited) */
+       unsigned int request_timeout_msecs;
+       /* max time to wait for connect() (and SSL handshake) to finish before
+          retrying (default = request_timeout_msecs) */
+       unsigned int connect_timeout_msecs;
+
        bool debug;
 };