]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: http-client-connection - Split off http_client_connection_process_response()
authorStephan Bosch <stephan.bosch@open-xchange.com>
Thu, 19 Nov 2020 10:57:07 +0000 (11:57 +0100)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Fri, 7 Mar 2025 14:56:56 +0000 (14:56 +0000)
src/lib-http/http-client-connection.c

index 084a5026a8759ab6e2ddd8e81c4c5b4be7f97240..ce59be1181dedecde22b6fb4c1b2bc427c215581 100644 (file)
@@ -948,6 +948,196 @@ http_client_request_add_event_headers(struct http_client_request *req,
        return str_c(str);
 }
 
+/* Process incoming response. Returns -1 when no more responses should be
+   handled (e.g. upon error or connection close), zero if this (1xx) response is
+   ignored, or 1 when this response was accepted as the final response and
+   the request was finished.
+ */
+static int
+http_client_connection_process_response(struct http_client_connection *conn,
+                                       struct http_client_request *req,
+                                       struct http_response *resp)
+{
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
+       struct http_client_request *req_ref;
+       bool aborted, early = FALSE;
+
+       if (req == NULL) {
+               /* Server sent response without any requests in the wait
+                  list */
+               if (resp->status == 408) {
+                       e_debug(conn->event,
+                               "Server explicitly closed connection: "
+                               "408 %s", resp->reason);
+               } else {
+                       e_debug(conn->event,
+                               "Got unexpected input from server: "
+                               "%u %s", resp->status,
+                               resp->reason);
+               }
+               http_client_connection_close(&conn);
+               return -1;
+       }
+
+       req->response_time = ioloop_timeval;
+       req->response_offset =
+               http_response_parser_get_last_offset(conn->http_parser);
+       i_assert(req->response_offset != UOFF_T_MAX);
+       i_assert(req->response_offset < conn->conn.input->v_offset);
+       req->bytes_in = conn->conn.input->v_offset - req->response_offset;
+
+       /* Got some response; cancel response timeout */
+       timeout_remove(&conn->to_response);
+
+       /* RFC 7231, Section 6.2:
+
+          A client MUST be able to parse one or more 1xx responses received
+          prior to a final response, even if the client does not expect one.
+          A user agent MAY ignore unexpected 1xx responses.
+        */
+       if (req->payload_sync && resp->status == 100) {
+               if (req->payload_sync_continue) {
+                       e_debug(conn->event,
+                               "Got 100-continue response after timeout");
+                       return 0;
+               }
+
+               pshared->no_payload_sync = FALSE;
+               pshared->seen_100_response = TRUE;
+               req->payload_sync_continue = TRUE;
+
+               e_debug(conn->event,
+                       "Got expected 100-continue response");
+
+               if (req->state == HTTP_REQUEST_STATE_ABORTED) {
+                       e_debug(conn->event,
+                               "Request aborted before sending payload was complete.");
+                       http_client_connection_close(&conn);
+                       return -1;
+               }
+
+               if (conn->conn.output != NULL)
+                       o_stream_set_flush_pending(conn->conn.output, TRUE);
+               return -1;
+       } else if (resp->status / 100 == 1) {
+               /* Ignore other 1xx for now */
+               e_debug(conn->event,
+                       "Got unexpected %u response; ignoring",
+                       resp->status);
+               return 0;
+       } else if ((!req->payload_sync || req->payload_sync_continue) &&
+                  !req->payload_finished &&
+                  req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT) {
+               /* Got early response from server while we're still sending
+                  request payload. we cannot recover from this reliably, so we
+                  stop sending payload and close the connection once the
+                  response is processed */
+               e_debug(conn->event,
+                       "Got early input from server; "
+                       "request payload not completely sent "
+                       "(will close connection)");
+               o_stream_unset_flush_callback(conn->conn.output);
+               conn->output_broken = early = TRUE;
+       }
+
+       const char *suffix =
+               http_client_request_add_event_headers(req, resp);
+       e_debug(conn->event,
+               "Got %u response for request %s: %s%s "
+               "(took %lld ms + %lld ms in queue)",
+               resp->status, http_client_request_label(req),
+               resp->reason, suffix,
+               timeval_diff_msecs(&req->response_time, &req->sent_time),
+               timeval_diff_msecs(&req->sent_time, &req->submit_time));
+
+       /* Make sure connection output is unlocked if 100-continue failed */
+       if (req->payload_sync && !req->payload_sync_continue) {
+               e_debug(conn->event, "Unlocked output");
+               conn->output_locked = FALSE;
+       }
+
+       /* Remove request from queue */
+       array_pop_front(&conn->request_wait_list);
+       aborted = (req->state == HTTP_REQUEST_STATE_ABORTED);
+       req_ref = req;
+       if (!http_client_connection_unref_request(conn, &req_ref)) {
+               i_assert(aborted);
+               req = NULL;
+       }
+
+       conn->close_indicated = resp->connection_close;
+
+       if (!aborted) {
+               bool handled = FALSE;
+
+               /* Response cannot be 2xx if request payload was not
+                  completely sent */
+               if (early && resp->status / 100 == 2) {
+                       http_client_request_error(
+                               &req, HTTP_CLIENT_REQUEST_ERROR_BAD_RESPONSE,
+                               "Server responded with success response "
+                               "before all payload was sent");
+                       http_client_connection_close(&conn);
+                       return -1;
+               }
+
+               /* Don't redirect/retry if we're sending data in small blocks
+                  via http_client_request_send_payload() and we're not waiting
+                  for 100 continue */
+               if (!req->payload_wait ||
+                   (req->payload_sync && !req->payload_sync_continue)) {
+                       /* Failed Expect: */
+                       if (resp->status == 417 && req->payload_sync) {
+                               /* Drop Expect: continue */
+                               req->payload_sync = FALSE;
+                               conn->output_locked = FALSE;
+                               pshared->no_payload_sync = TRUE;
+                               if (http_client_request_try_retry(req))
+                                       handled = TRUE;
+                       /* Redirection */
+                       } else if (req->client->set->auto_redirect &&
+                                  resp->status / 100 == 3 &&
+                                  resp->status != 304 &&
+                                  resp->location != NULL) {
+                               /* Redirect (possibly after delay) */
+                               if (http_client_request_delay_from_response(
+                                       req, resp) >= 0) {
+                                       http_client_request_redirect(
+                                               req, resp->status,
+                                               resp->location);
+                                       handled = TRUE;
+                               }
+                       /* Service unavailable */
+                       } else if (resp->status == 503) {
+                               /* Automatically retry after delay if indicated
+                                */
+                               if (resp->retry_after != (time_t)-1 &&
+                                   http_client_request_delay_from_response(
+                                       req, resp) > 0 &&
+                                   http_client_request_try_retry(req))
+                                       handled = TRUE;
+                       /* Request timeout (by server) */
+                       } else if (resp->status == 408) {
+                               /* Automatically retry */
+                               if (http_client_request_try_retry(req))
+                                       handled = TRUE;
+                               /* Connection close is implicit, although server
+                                  should indicate that explicitly */
+                               conn->close_indicated = TRUE;
+                       }
+               }
+
+               if (!handled) {
+                       /* Response for application */
+                       if (!http_client_connection_return_response(
+                               conn, req, resp))
+                               return -1;
+               }
+       }
+
+       return 1;
+}
+
 static void http_client_connection_input(struct connection *_conn)
 {
        struct http_client_connection *conn =
@@ -956,7 +1146,7 @@ static void http_client_connection_input(struct connection *_conn)
        struct http_client_peer_shared *pshared = conn->ppool->peer;
        struct http_response response;
        struct http_client_request *const *reqs;
-       struct http_client_request *req = NULL, *req_ref;
+       struct http_client_request *req = NULL;
        enum http_response_payload_type payload_type;
        unsigned int count;
        int finished = 0, ret;
@@ -1040,184 +1230,12 @@ static void http_client_connection_input(struct connection *_conn)
 
        while ((ret = http_response_parse_next(conn->http_parser, payload_type,
                                               &response, &error)) > 0) {
-               bool aborted, early = FALSE;
-
-               if (req == NULL) {
-                       /* Server sent response without any requests in the wait
-                          list */
-                       if (response.status == 408) {
-                               e_debug(conn->event,
-                                       "Server explicitly closed connection: "
-                                       "408 %s", response.reason);
-                       } else {
-                               e_debug(conn->event,
-                                       "Got unexpected input from server: "
-                                       "%u %s", response.status,
-                                       response.reason);
-                       }
-                       http_client_connection_close(&conn);
-                       return;
-               }
-
-               req->response_time = ioloop_timeval;
-               req->response_offset =
-                       http_response_parser_get_last_offset(conn->http_parser);
-               i_assert(req->response_offset != UOFF_T_MAX);
-               i_assert(req->response_offset < conn->conn.input->v_offset);
-               req->bytes_in = conn->conn.input->v_offset -
-                       req->response_offset;
-
-               /* Got some response; cancel response timeout */
-               timeout_remove(&conn->to_response);
-
-               /* RFC 7231, Section 6.2:
-
-                  A client MUST be able to parse one or more 1xx responses
-                  received prior to a final response, even if the client does
-                  not expect one. A user agent MAY ignore unexpected 1xx
-                  responses.
-                */
-               if (req->payload_sync && response.status == 100) {
-                       if (req->payload_sync_continue) {
-                               e_debug(conn->event,
-                                       "Got 100-continue response after timeout");
-                               continue;
-                       }
-
-                       pshared->no_payload_sync = FALSE;
-                       pshared->seen_100_response = TRUE;
-                       req->payload_sync_continue = TRUE;
-
-                       e_debug(conn->event,
-                               "Got expected 100-continue response");
-
-                       if (req->state == HTTP_REQUEST_STATE_ABORTED) {
-                               e_debug(conn->event,
-                                       "Request aborted before sending payload was complete.");
-                               http_client_connection_close(&conn);
-                               return;
-                       }
-
-                       if (conn->conn.output != NULL)
-                               o_stream_set_flush_pending(conn->conn.output, TRUE);
+               ret = http_client_connection_process_response(conn, req,
+                                                             &response);
+               if (ret < 0)
                        return;
-               } else if (response.status / 100 == 1) {
-                       /* Ignore other 1xx for now */
-                       e_debug(conn->event,
-                               "Got unexpected %u response; ignoring",
-                               response.status);
+               if (ret == 0)
                        continue;
-               } else if ((!req->payload_sync || req->payload_sync_continue) &&
-                          !req->payload_finished &&
-                          req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT) {
-                       /* Got early response from server while we're still
-                          sending request payload. we cannot recover from this
-                          reliably, so we stop sending payload and close the
-                          connection once the response is processed */
-                       e_debug(conn->event,
-                               "Got early input from server; "
-                               "request payload not completely sent "
-                               "(will close connection)");
-                       o_stream_unset_flush_callback(conn->conn.output);
-                       conn->output_broken = early = TRUE;
-               }
-
-               const char *suffix =
-                       http_client_request_add_event_headers(req, &response);
-               e_debug(conn->event,
-                       "Got %u response for request %s: %s%s "
-                       "(took %lld ms + %lld ms in queue)",
-                       response.status, http_client_request_label(req),
-                       response.reason, suffix,
-                       timeval_diff_msecs(&req->response_time, &req->sent_time),
-                       timeval_diff_msecs(&req->sent_time, &req->submit_time));
-
-               /* Make sure connection output is unlocked if 100-continue
-                  failed */
-               if (req->payload_sync && !req->payload_sync_continue) {
-                       e_debug(conn->event, "Unlocked output");
-                       conn->output_locked = FALSE;
-               }
-
-               /* Remove request from queue */
-               array_pop_front(&conn->request_wait_list);
-               aborted = (req->state == HTTP_REQUEST_STATE_ABORTED);
-               req_ref = req;
-               if (!http_client_connection_unref_request(conn, &req_ref)) {
-                       i_assert(aborted);
-                       req = NULL;
-               }
-
-               conn->close_indicated = response.connection_close;
-
-               if (!aborted) {
-                       bool handled = FALSE;
-
-                       /* Response cannot be 2xx if request payload was not
-                          completely sent */
-                       if (early && response.status / 100 == 2) {
-                               http_client_request_error(
-                                       &req, HTTP_CLIENT_REQUEST_ERROR_BAD_RESPONSE,
-                                       "Server responded with success response "
-                                       "before all payload was sent");
-                               http_client_connection_close(&conn);
-                               return;
-                       }
-
-                       /* Don't redirect/retry if we're sending data in small
-                          blocks via http_client_request_send_payload()
-                          and we're not waiting for 100 continue */
-                       if (!req->payload_wait ||
-                           (req->payload_sync && !req->payload_sync_continue)) {
-                               /* Failed Expect: */
-                               if (response.status == 417 && req->payload_sync) {
-                                       /* Drop Expect: continue */
-                                       req->payload_sync = FALSE;
-                                       conn->output_locked = FALSE;
-                                       pshared->no_payload_sync = TRUE;
-                                       if (http_client_request_try_retry(req))
-                                               handled = TRUE;
-                               /* Redirection */
-                               } else if (req->client->set->auto_redirect &&
-                                          response.status / 100 == 3 &&
-                                          response.status != 304 &&
-                                          response.location != NULL) {
-                                       /* Redirect (possibly after delay) */
-                                       if (http_client_request_delay_from_response(
-                                               req, &response) >= 0) {
-                                               http_client_request_redirect(
-                                                       req, response.status,
-                                                       response.location);
-                                               handled = TRUE;
-                                       }
-                               /* Service unavailable */
-                               } else if (response.status == 503) {
-                                       /* Automatically retry after delay if
-                                          indicated */
-                                       if (response.retry_after != (time_t)-1 &&
-                                           http_client_request_delay_from_response(
-                                               req, &response) > 0 &&
-                                           http_client_request_try_retry(req))
-                                               handled = TRUE;
-                               /* Request timeout (by server) */
-                               } else if (response.status == 408) {
-                                       /* Automatically retry */
-                                       if (http_client_request_try_retry(req))
-                                               handled = TRUE;
-                                       /* Connection close is implicit,
-                                          although server should indicate that
-                                          explicitly */
-                                       conn->close_indicated = TRUE;
-                               }
-                       }
-
-                       if (!handled) {
-                               /* Response for application */
-                               if (!http_client_connection_return_response(
-                                       conn, req, &response))
-                                       return;
-                       }
-               }
 
                finished++;