# HG changeset patch # User Stephan Bosch # Date 1455139507 -3600 # Node ID 7527051eb56a79c8e4df628ff9b2114e75d5f458 # Parent b445fef190923759b3381b9f11fa89c00c5d8321 lib-http: server: Created blocking http_server_response_send_payload() API that closely mimics the client equivalent. It allows sending response payload in several chunks in a blocking fashion. diff -r b445fef19092 -r 7527051eb56a src/lib-http/http-server-connection.c --- a/src/lib-http/http-server-connection.c Wed Feb 10 22:19:27 2016 +0100 +++ b/src/lib-http/http-server-connection.c Wed Feb 10 22:25:07 2016 +0100 @@ -111,7 +111,8 @@ http_server_connection_input_resume(struct http_server_connection *conn) { if (conn->conn.io == NULL && !conn->closed && - !conn->input_broken && !conn->close_indicated) { + !conn->input_broken && !conn->close_indicated && + !conn->in_req_callback) { conn->conn.io = io_add(conn->conn.fd_in, IO_READ, http_server_connection_input, &conn->conn); } @@ -255,7 +256,7 @@ somewhere from the API user's code, which we can't really know what state it is in). this call also triggers sending the next response if necessary. */ - if (!conn->input_broken) { + if (!conn->input_broken && !conn->in_req_callback) { conn->to_input = timeout_add_short(0, http_server_payload_destroyed_timeout, conn); } @@ -299,6 +300,7 @@ { struct istream *payload; + i_assert(!conn->in_req_callback); i_assert(conn->incoming_payload == NULL); if (req->req.version_major != 1) { @@ -323,11 +325,13 @@ our one before calling it */ http_server_connection_input_halt(conn); + conn->in_req_callback = TRUE; http_server_connection_request_callback(conn, req); if (conn->closed) { /* the callback managed to get this connection destroyed/closed */ return FALSE; } + conn->in_req_callback = FALSE; if (req->req.payload != NULL) { /* send 100 Continue when appropriate */ @@ -358,7 +362,8 @@ } if (conn->incoming_payload == NULL) { - i_assert(conn->conn.io != NULL); + if (conn->conn.io == NULL && conn->to_input == NULL) + http_server_connection_input_resume(conn); return TRUE; } @@ -391,6 +396,50 @@ return 0; } +static bool +http_server_connection_check_input(struct http_server_connection *conn) +{ + struct istream *input = conn->conn.input; + int stream_errno; + + if (input == NULL) + return FALSE; + stream_errno = input->stream_errno; + + if (input->eof || stream_errno != 0) { + /* connection input broken; output may still be intact */ + if (stream_errno != 0 && stream_errno != EPIPE && + stream_errno != ECONNRESET) { + http_server_connection_client_error(conn, + "Connection lost: read(%s) failed: %s", + i_stream_get_name(input), + i_stream_get_error(input)); + http_server_connection_close(&conn, "Read failure"); + } else { + http_server_connection_debug(conn, + "Connection lost: Remote disconnected"); + + if (conn->request_queue_head == NULL) { + /* no pending requests; close */ + http_server_connection_close(&conn, + "Remote closed connection"); + } else if (conn->request_queue_head->state < + HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE) { + /* unfinished request; close */ + http_server_connection_close(&conn, + "Remote closed connection unexpectedly"); + } else { + /* a request is still processing; only drop input io for now. + the other end may only have shutdown one direction */ + conn->input_broken = TRUE; + http_server_connection_input_halt(conn); + } + } + return FALSE; + } + return TRUE; +} + static void http_server_connection_input(struct connection *_conn) { struct http_server_connection *conn = @@ -401,6 +450,7 @@ bool cont; int ret; + i_assert(!conn->in_req_callback); i_assert(!conn->input_broken && conn->incoming_payload == NULL); i_assert(!conn->close_indicated); @@ -520,37 +570,8 @@ } if (ret <= 0 && - (conn->conn.input->eof || conn->conn.input->stream_errno != 0)) { - int stream_errno = conn->conn.input->stream_errno; - - /* connection input broken; output may still be intact */ - if (stream_errno != 0 && stream_errno != EPIPE && - stream_errno != ECONNRESET) { - http_server_connection_client_error(conn, - "Connection lost: read(%s) failed: %s", - i_stream_get_name(conn->conn.input), strerror(stream_errno)); - http_server_connection_close(&conn, "Read failure"); - } else { - http_server_connection_debug(conn, - "Connection lost: Remote disconnected"); - - if (conn->request_queue_head == NULL) { - /* no pending requests; close */ - http_server_connection_close(&conn, "Remote closed connection"); - } else if (conn->request_queue_head->state < - HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE) { - /* unfinished request; close */ - http_server_connection_close(&conn, - "Remote closed connection unexpectedly"); - } else { - /* a request is still processing; only drop input io for now. - the other end may only have shutdown one direction */ - conn->input_broken = TRUE; - http_server_connection_input_halt(conn); - } - } + !http_server_connection_check_input(conn)) return; - } if (ret < 0) { http_server_connection_ref(conn); @@ -606,6 +627,118 @@ } } +static void +http_server_connection_discard_input(struct http_server_connection *conn) +{ + struct http_server *server = conn->server; + enum http_request_parse_error error_code; + const char *error; + int ret = 0; + + i_assert(server->ioloop != NULL); + + ret = http_request_parse_finish_payload + (conn->http_parser, &error_code, &error); + + if (ret <= 0 && + !http_server_connection_check_input(conn)) { + io_loop_stop(server->ioloop); + return; + } + + if (ret < 0) { + http_server_connection_ref(conn); + + http_server_connection_client_error(conn, + "Client sent invalid request: %s", error); + + switch (error_code) { + case HTTP_REQUEST_PARSE_ERROR_PAYLOAD_TOO_LARGE: + conn->input_broken = TRUE; + http_server_request_fail_close(conn->request_queue_head, + 413, "Payload Too Large"); + break; + default: + i_unreached(); + } + + http_server_connection_unref(&conn); + io_loop_stop(server->ioloop); + return; + } + + if (ret > 0) + io_loop_stop(server->ioloop); +} + +int http_server_connection_discard_payload( + struct http_server_connection *conn) +{ + struct http_server *server = conn->server; + struct ioloop *prev_ioloop = current_ioloop; + + i_assert(conn->conn.io == NULL); + i_assert(server->ioloop == NULL); + + /* destroy payload wrapper early to advance state */ + if (conn->incoming_payload != NULL) { + i_stream_unref(&conn->incoming_payload); + conn->incoming_payload = NULL; + } + + /* finish reading payload from the parser */ + if (http_request_parser_pending_payload + (conn->http_parser)) { + http_server_connection_debug(conn, + "Discarding remaining incoming payload"); + + server->ioloop = io_loop_create(); + http_server_connection_switch_ioloop(conn); + io_loop_set_running(server->ioloop); + + conn->conn.io = io_add_istream(conn->conn.input, + http_server_connection_discard_input, conn); + http_server_connection_discard_input(conn); + if (io_loop_is_running(server->ioloop)) + io_loop_run(server->ioloop); + io_remove(&conn->conn.io); + + io_loop_set_current(prev_ioloop); + http_server_connection_switch_ioloop(conn); + io_loop_set_current(server->ioloop); + io_loop_destroy(&server->ioloop); + } else { + http_server_connection_debug(conn, + "No remaining incoming payload"); + } + + /* check whether connection is still viable */ + http_server_connection_ref(conn); + (void)http_server_connection_check_input(conn); + http_server_connection_unref(&conn); + if (conn == NULL || conn->closed) + return -1; + return 0; +} + +void http_server_connection_write_failed(struct http_server_connection *conn, + const char *error) +{ + if (conn->closed) + return; + + if (error != NULL) { + http_server_connection_error(conn, + "Connection lost: %s", error); + http_server_connection_close(&conn, "Write failure"); + } else { + http_server_connection_debug(conn, + "Connection lost: Remote disconnected"); + http_server_connection_close(&conn, + "Remote closed connection unexpectedly"); + } +} + static bool http_server_connection_next_response(struct http_server_connection *conn) { @@ -641,18 +774,17 @@ struct ostream *output = conn->conn.output; if (o_stream_send(output, response, strlen(response)) < 0) { - if (errno != EPIPE && errno != ECONNRESET) { - http_server_connection_error(conn, - "Failed to send 100 response: write(%s) failed: %m", - o_stream_get_name(output)); - http_server_connection_close(&conn, "Write failure"); - } else { - http_server_connection_debug(conn, - "Failed to send 100 response: Remote disconnected"); - http_server_connection_close(&conn, - "Remote closed connection"); + if (output->stream_errno != EPIPE && + output->stream_errno != ECONNRESET) { + error = t_strdup_printf("write(%s) failed: %s", + o_stream_get_name(output), + o_stream_get_error(output)); } + http_server_connection_write_failed(conn, error); + return FALSE; } + + http_server_connection_debug(conn, "Sent 100 Continue"); req->sent_100_continue = TRUE; } return FALSE; @@ -668,16 +800,7 @@ http_server_request_unref(&req); if (ret < 0) { - if (error != NULL) { - http_server_connection_error(conn, - "Failed to send response: %s", error); - http_server_connection_close(&conn, "Write failure"); - } else { - http_server_connection_debug(conn, - "Failed to send response: Remote disconnected"); - http_server_connection_close(&conn, - "Remote closed connection"); - } + http_server_connection_write_failed(conn, error); return FALSE; } @@ -707,30 +830,34 @@ return 0; } -int http_server_connection_output(struct http_server_connection *conn) +int http_server_connection_flush(struct http_server_connection *conn) { struct ostream *output = conn->conn.output; - const char *error = NULL; int ret; if ((ret = o_stream_flush(output)) <= 0) { if (ret < 0) { - if (errno != EPIPE && errno != ECONNRESET) { - http_server_connection_error(conn, - "Connection lost: write(%s) failed: %m", - o_stream_get_name(output)); - http_server_connection_close(&conn, "Write failure"); - } else { - http_server_connection_debug(conn, - "Connection lost: Remote disconnected"); - http_server_connection_close(&conn, - "Remote closed connection unexpectedly"); + const char *error = NULL; + + if (output->stream_errno != EPIPE && + output->stream_errno != ECONNRESET) { + error = t_strdup_printf("write(%s) failed: %s", + o_stream_get_name(output), + o_stream_get_error(output)); } + http_server_connection_write_failed(conn, error); } return -1; } http_server_connection_timeout_reset(conn); + return 0; +} + +int http_server_connection_output(struct http_server_connection *conn) +{ + if (http_server_connection_flush(conn) < 0) + return -1; if (!conn->output_locked) { if (http_server_connection_send_responses(conn) < 0) @@ -738,19 +865,11 @@ } else if (conn->request_queue_head != NULL) { struct http_server_request *req = conn->request_queue_head; struct http_server_response *resp = req->response; + const char *error = NULL; i_assert(resp != NULL); if (http_server_response_send_more(resp, &error) < 0) { - if (error != NULL ) { - http_server_connection_error(conn, - "Connection lost: %s", error); - http_server_connection_close(&conn, "Write failure"); - } else { - http_server_connection_debug(conn, - "Connection lost: Remote disconnected"); - http_server_connection_close(&conn, - "Remote closed connection unexpectedly"); - } + http_server_connection_write_failed(conn, error); return -1; } @@ -879,7 +998,7 @@ req = conn->request_queue_head; while (req != NULL) { req_next = req->next; - http_server_request_abort(&req); + http_server_request_abort(&req, NULL); req = req_next; } diff -r b445fef19092 -r 7527051eb56a src/lib-http/http-server-private.h --- a/src/lib-http/http-server-private.h Wed Feb 10 22:19:27 2016 +0100 +++ b/src/lib-http/http-server-private.h Wed Feb 10 22:25:07 2016 +0100 @@ -61,6 +61,9 @@ unsigned int have_hdr_body_spec:1; unsigned int payload_chunked:1; + unsigned int payload_blocking:1; + unsigned int payload_direct:1; + unsigned int payload_corked:1; unsigned int close:1; unsigned int submitted:1; }; @@ -119,6 +122,7 @@ unsigned int close_indicated:1; unsigned int input_broken:1; unsigned int output_locked:1; + unsigned int in_req_callback:1; /* performing request callback (busy) */ }; struct http_server { @@ -167,7 +171,8 @@ struct http_server_request * http_server_request_new(struct http_server_connection *conn); void http_server_request_destroy(struct http_server_request **_req); -void http_server_request_abort(struct http_server_request **_req); +void http_server_request_abort(struct http_server_request **_req, + const char *reason) ATTR_NULL(2); void http_server_request_halt_payload(struct http_server_request *req); void http_server_request_continue_payload(struct http_server_request *req); @@ -202,11 +207,20 @@ struct connection_list *http_server_connection_list_init(void); void http_server_connection_switch_ioloop(struct http_server_connection *conn); + +void http_server_connection_write_failed(struct http_server_connection *conn, + const char *error); + void http_server_connection_trigger_responses( struct http_server_connection *conn); +int http_server_connection_flush(struct http_server_connection *conn); int http_server_connection_output(struct http_server_connection *conn); + void http_server_connection_tunnel(struct http_server_connection **_conn, http_server_tunnel_callback_t callback, void *context); + +int http_server_connection_discard_payload( + struct http_server_connection *conn); bool http_server_connection_pending_payload(struct http_server_connection *conn); static inline void http_server_connection_add_request(struct http_server_connection *conn, diff -r b445fef19092 -r 7527051eb56a src/lib-http/http-server-request.c --- a/src/lib-http/http-server-request.c Wed Feb 10 22:19:27 2016 +0100 +++ b/src/lib-http/http-server-request.c Wed Feb 10 22:25:07 2016 +0100 @@ -1,6 +1,8 @@ /* Copyright (c) 2013-2016 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "ioloop.h" +#include "ostream.h" #include "http-server-private.h" @@ -88,9 +90,17 @@ void http_server_request_destroy(struct http_server_request **_req) { struct http_server_request *req = *_req; + struct http_server *server = req->server; http_server_request_debug(req, "Destroy"); + /* just make sure the request ends in a proper state */ + if (req->state < HTTP_SERVER_REQUEST_STATE_FINISHED) + req->state = HTTP_SERVER_REQUEST_STATE_ABORTED; + + if (server->ioloop) + io_loop_stop(server->ioloop); + if (req->delay_destroy) { req->destroy_pending = TRUE; } else if (req->destroy_callback != NULL) { @@ -110,22 +120,46 @@ req->destroy_context = context; } -void http_server_request_abort(struct http_server_request **_req) +void http_server_request_abort(struct http_server_request **_req, + const char *reason) { struct http_server_request *req = *_req; struct http_server_connection *conn = req->conn; http_server_request_debug(req, "Abort"); + req->conn = NULL; if (req->state < HTTP_SERVER_REQUEST_STATE_FINISHED) { + if (conn != NULL) { + http_server_connection_remove_request(conn, req); + + if (!conn->closed) { + /* send best-effort response if appropriate */ + if (!conn->output_locked && + req->state >= HTTP_SERVER_REQUEST_STATE_PROCESSING && + req->state < HTTP_SERVER_REQUEST_STATE_SENT_RESPONSE) { + static const char *response = + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Length: 0\r\n" + "\r\n"; + + (void)o_stream_send(conn->conn.output, + response, strlen(response)); + } + + /* close the connection */ + http_server_connection_close(&conn, reason); + } + } + req->state = HTTP_SERVER_REQUEST_STATE_ABORTED; - http_server_connection_remove_request(conn, req); } - if (req->response != NULL) + if (req->response != NULL && + !req->response->payload_blocking) { http_server_response_free(req->response); - req->response = NULL; - req->conn = conn; + req->response = NULL; + } http_server_request_destroy(_req); } diff -r b445fef19092 -r 7527051eb56a src/lib-http/http-server-response.c --- a/src/lib-http/http-server-response.c Wed Feb 10 22:19:27 2016 +0100 +++ b/src/lib-http/http-server-response.c Wed Feb 10 22:25:07 2016 +0100 @@ -4,11 +4,18 @@ #include "str.h" #include "array.h" #include "istream.h" -#include "ostream.h" +#include "ostream-private.h" #include "http-date.h" #include "http-transfer.h" #include "http-server-private.h" +struct http_server_response_payload { + struct http_server_response *resp; + struct const_iovec *iov; + unsigned int iov_count, iov_idx; + size_t iov_pos; +}; + /* * Logging */ @@ -58,6 +65,8 @@ { http_server_response_debug(resp, "Destroy"); + i_assert(!resp->payload_blocking); + if (resp->payload_input != NULL) i_stream_unref(&resp->payload_input); if (resp->payload_output != NULL) @@ -211,16 +220,223 @@ static void http_server_response_finish_payload_out(struct http_server_response *resp) { + struct http_server_connection *conn = resp->request->conn; + if (resp->payload_output != NULL) { o_stream_unref(&resp->payload_output); resp->payload_output = NULL; } - resp->request->conn->output_locked = FALSE; + http_server_response_debug(resp, "Finished sending payload"); + conn->output_locked = FALSE; + if (resp->payload_corked) + o_stream_uncork(conn->conn.output); + o_stream_set_flush_callback(conn->conn.output, + http_server_connection_output, conn); + http_server_request_finished(resp->request); } +static int +http_server_response_output_direct(struct http_server_response_payload *rpay) +{ + struct http_server_response *resp = rpay->resp; + struct http_server_connection *conn = resp->request->conn; + struct http_server *server = resp->request->server; + struct ostream *output = resp->payload_output; + struct const_iovec *iov; + unsigned int iov_count, i; + size_t bytes_left, block_len; + ssize_t ret; + + if (http_server_connection_flush(conn) < 0) + return -1; + + iov = &rpay->iov[rpay->iov_idx]; + iov_count = rpay->iov_count - rpay->iov_idx; + + if ((ret=o_stream_sendv(output, iov, iov_count)) < 0) { + const char *error = NULL; + + if (output->stream_errno != EPIPE && + output->stream_errno != ECONNRESET) { + error = t_strdup_printf("write(%s) failed: %s", + o_stream_get_name(output), + o_stream_get_error(output)); + } + http_server_connection_write_failed(conn, error); + return -1; + } + if (ret > 0) { + bytes_left = ret; + for (i = 0; i < iov_count && bytes_left > 0; i++) { + block_len = iov[i].iov_len <= bytes_left ? + iov[i].iov_len : bytes_left; + bytes_left -= block_len; + } + rpay->iov_idx += i; + if (i < iov_count) { + i_assert(iov[i].iov_len > bytes_left); + iov[i].iov_base = PTR_OFFSET + (iov[i].iov_base, iov[i].iov_len - bytes_left); + iov[i].iov_len = bytes_left; + } else { + i_assert(rpay->iov_idx == rpay->iov_count); + i_assert(server->ioloop != NULL); + io_loop_stop(server->ioloop); + } + } + return 1; +} + +static int +http_server_response_output_payload( + struct http_server_response **_resp, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct ioloop *prev_ioloop = current_ioloop; + struct http_server_response *resp = *_resp; + struct http_server_request *req = resp->request; + struct http_server *server = req->server; + struct http_server_connection *conn = req->conn; + struct http_server_response_payload rpay; + int ret; + + i_assert(req->state < HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE || + req->state == HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT); + i_assert(resp->payload_input == NULL); + + /* Discard any remaining incoming payload */ + if (http_server_connection_discard_payload(conn) < 0) + return -1; + req->req.payload = NULL; + + http_server_connection_ref(conn); + http_server_request_ref(req); + resp->payload_blocking = TRUE; + + memset(&rpay, 0, sizeof(rpay)); + rpay.resp = resp; + + if (iov == NULL) { + resp->payload_direct = FALSE; + if (req->state == HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT) + http_server_response_finish_payload_out(resp); + } else { + resp->payload_direct = TRUE; + rpay.iov = i_new(struct const_iovec, iov_count); + memcpy(rpay.iov, iov, sizeof(*iov)*iov_count); + rpay.iov_count = iov_count; + } + + resp->payload_size = 0; + resp->payload_chunked = TRUE; + + if (req->state < HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE) + http_server_response_submit(resp); + + if (req->state < HTTP_SERVER_REQUEST_STATE_FINISHED) { + /* Wait for payload data to be written */ + + i_assert(server->ioloop == NULL); + server->ioloop = io_loop_create(); + http_server_connection_switch_ioloop(conn); + + do { + if (req->state < HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT) { + http_server_response_debug(resp, + "Preparing to send blocking payload"); + http_server_connection_trigger_responses(conn); + + } else if (resp->payload_output != NULL) { + http_server_response_debug(resp, + "Sending blocking payload"); + o_stream_unset_flush_callback(conn->conn.output); + o_stream_set_flush_callback(resp->payload_output, + http_server_response_output_direct, &rpay); + o_stream_set_flush_pending(resp->payload_output, TRUE); + + } else { + http_server_response_finish_payload_out(resp); + i_assert(req->state >= HTTP_SERVER_REQUEST_STATE_FINISHED); + break; + } + + io_loop_run(server->ioloop); + + if (rpay.iov_count > 0 && rpay.iov_idx >= rpay.iov_count) + break; + } while (req->state < HTTP_SERVER_REQUEST_STATE_FINISHED); + + io_loop_set_current(prev_ioloop); + http_server_connection_switch_ioloop(conn); + io_loop_set_current(server->ioloop); + io_loop_destroy(&server->ioloop); + } + + switch (req->state) { + case HTTP_SERVER_REQUEST_STATE_FINISHED: + ret = 1; + break; + case HTTP_SERVER_REQUEST_STATE_ABORTED: + http_server_response_debug(resp, + "Request aborted while sending blocking payload"); + ret = -1; + break; + default: + ret = 0; + break; + } + + resp->payload_blocking = FALSE; + resp->payload_direct = FALSE; + + /* callback may have messed with our pointer, + so unref using local variable */ + http_server_request_unref(&req); + if (req == NULL) + *_resp = NULL; + + http_server_connection_unref(&conn); + i_free(rpay.iov); + + /* Return status */ + return ret; +} + +int http_server_response_send_payload(struct http_server_response **_resp, + const unsigned char *data, size_t size) +{ + struct http_server_response *resp = *_resp; + struct const_iovec iov; + + resp->payload_corked = TRUE; + + i_assert(data != NULL); + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = data; + iov.iov_len = size; + return http_server_response_output_payload(_resp, &iov, 1); +} + +int http_server_response_finish_payload(struct http_server_response **_resp) +{ + return http_server_response_output_payload(_resp, NULL, 0); +} + +void http_server_response_abort_payload(struct http_server_response **_resp) +{ + struct http_server_response *resp = *_resp; + struct http_server_request *req = resp->request; + + http_server_request_abort(&req, + "Aborted sending response payload"); + + *_resp = NULL; +} + static void http_server_response_payload_input(struct http_server_response *resp) { @@ -241,6 +457,7 @@ *error_r = NULL; + i_assert(!resp->payload_blocking); i_assert(resp->payload_input != NULL); i_assert(resp->payload_output != NULL); @@ -253,11 +470,14 @@ o_stream_set_max_buffer_size(output, (size_t)-1); if (resp->payload_input->stream_errno != 0) { + /* we're in the middle of sending a response, so the connection + will also have to be aborted */ errno = resp->payload_input->stream_errno; *error_r = t_strdup_printf("read(%s) failed: %m", i_stream_get_name(resp->payload_input)); ret = -1; } else if (output->stream_errno != 0) { + /* failed to send response */ errno = output->stream_errno; if (errno != EPIPE && errno != ECONNRESET) { *error_r = t_strdup_printf("write(%s) failed: %m", @@ -269,16 +489,17 @@ } if (ret < 0 || i_stream_is_eof(resp->payload_input)) { + /* finished sending */ if (ret >= 0 && !resp->payload_chunked && - resp->payload_input->v_offset - resp->payload_offset != resp->payload_size) { + resp->payload_input->v_offset - resp->payload_offset != + resp->payload_size) { *error_r = t_strdup_printf( "Input stream %s size changed unexpectedly", i_stream_get_name(resp->payload_input)); ret = -1; } - + /* finished sending payload */ http_server_response_finish_payload_out(resp); - } else if (i_stream_get_data_size(resp->payload_input) > 0) { /* output is blocking */ conn->output_locked = TRUE; @@ -298,6 +519,7 @@ { struct http_server_request *req = resp->request; struct http_server_connection *conn = req->conn; + struct http_server *server = req->server; struct ostream *output = conn->conn.output; string_t *rtext = t_str_new(256); struct const_iovec iov[3]; @@ -325,28 +547,30 @@ http_auth_create_challenges(rtext, &resp->auth_challenges); str_append(rtext, "\r\n"); } - if (resp->payload_chunked) { - if (http_server_request_version_equals(req, 1, 0)) { - /* cannot use Transfer-Encoding */ + if (resp->payload_input != NULL || resp->payload_direct) { + if (resp->payload_chunked) { + if (http_server_request_version_equals(req, 1, 0)) { + /* cannot use Transfer-Encoding */ + resp->payload_output = output; + o_stream_ref(output); + /* connection close marks end of payload */ + resp->close = TRUE; + } else { + if (!resp->have_hdr_body_spec) + str_append(rtext, "Transfer-Encoding: chunked\r\n"); + resp->payload_output = + http_transfer_chunked_ostream_create(output); + } + } else { + /* send Content-Length if we have specified a payload, + even if it's 0 bytes. */ + if (!resp->have_hdr_body_spec) { + str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n", + resp->payload_size); + } resp->payload_output = output; o_stream_ref(output); - /* connection close marks end of payload */ - resp->close = TRUE; - } else { - if (!resp->have_hdr_body_spec) - str_append(rtext, "Transfer-Encoding: chunked\r\n"); - resp->payload_output = - http_transfer_chunked_ostream_create(output); } - } else if (resp->payload_input != NULL) { - /* send Content-Length if we have specified a payload, - even if it's 0 bytes. */ - if (!resp->have_hdr_body_spec) { - str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n", - resp->payload_size); - } - resp->payload_output = output; - o_stream_ref(output); } else if (resp->tunnel_callback == NULL && resp->status / 100 != 1 && resp->status != 204 && resp->status != 304 && !http_request_method_is(&req->req, "HEAD")) { @@ -401,16 +625,26 @@ ret = -1; } - http_server_response_debug(resp, "Sent header"); + if (ret >= 0) { + http_server_response_debug(resp, "Sent header"); - if (ret >= 0 && resp->payload_output != NULL) { - if (http_server_response_send_more(resp, error_r) < 0) - ret = -1; - } else { - conn->output_locked = FALSE; - http_server_request_finished(resp->request); + if (resp->payload_blocking) { + /* blocking payload */ + conn->output_locked = TRUE; + if (server->ioloop != NULL) + io_loop_stop(server->ioloop); + } else if (resp->payload_output != NULL) { + /* non-blocking payload */ + if (http_server_response_send_more(resp, error_r) < 0) + ret = -1; + } else { + /* no payload to send */ + conn->output_locked = FALSE; + http_server_response_finish_payload_out(resp); + } } - o_stream_uncork(output); + if (!resp->payload_corked) + o_stream_uncork(output); o_stream_unref(&output); return ret; } diff -r b445fef19092 -r 7527051eb56a src/lib-http/http-server.h --- a/src/lib-http/http-server.h Wed Feb 10 22:19:27 2016 +0100 +++ b/src/lib-http/http-server.h Wed Feb 10 22:25:07 2016 +0100 @@ -168,4 +168,14 @@ void http_server_switch_ioloop(struct http_server *server); +/* submits response and blocks until provided payload is sent. Multiple calls + are allowed; payload transmission is finished with + http_server_response_finish_payload(). */ +int http_server_response_send_payload(struct http_server_response **resp, + const unsigned char *data, size_t size); +int http_server_response_finish_payload(struct http_server_response **resp); +/* abort response payload transmission prematurely. this closes the associated + connection */ +void http_server_response_abort_payload(struct http_server_response **resp); + #endif