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 =
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;
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++;