From: Stephan Bosch Date: Thu, 19 Nov 2020 10:57:07 +0000 (+0100) Subject: lib-http: http-client-connection - Split off http_client_connection_process_response() X-Git-Tag: 2.4.1~74 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a6be5bb3707e5b1c998fa8570d53f890bb669e2e;p=thirdparty%2Fdovecot%2Fcore.git lib-http: http-client-connection - Split off http_client_connection_process_response() --- diff --git a/src/lib-http/http-client-connection.c b/src/lib-http/http-client-connection.c index 084a5026a8..ce59be1181 100644 --- a/src/lib-http/http-client-connection.c +++ b/src/lib-http/http-client-connection.c @@ -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++;