]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: server: Created blocking http_server_response_send_payload() API that close...
authorStephan Bosch <stephan@rename-it.nl>
Wed, 10 Feb 2016 21:25:07 +0000 (22:25 +0100)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Thu, 11 Feb 2016 10:00:42 +0000 (12:00 +0200)
It allows sending response payload in several chunks in a blocking fashion.

src/lib-http/http-server-connection.c
src/lib-http/http-server-private.h
src/lib-http/http-server-request.c
src/lib-http/http-server-response.c
src/lib-http/http-server.h

index 2e9a56ad79a6e4aa7cafe26eb6f4ad66888a1416..1f2570fb3545bc945798171d9bbe67b1e6d2c00c 100644 (file)
@@ -111,7 +111,8 @@ static void
 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 @@ static void http_server_payload_destroyed(struct http_server_request *req)
           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 @@ http_server_connection_handle_request(struct http_server_connection *conn,
 {
        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 @@ http_server_connection_handle_request(struct http_server_connection *conn,
           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 @@ http_server_connection_handle_request(struct http_server_connection *conn,
        }
 
        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 @@ http_server_connection_ssl_init(struct http_server_connection *conn)
        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 @@ static void http_server_connection_input(struct connection *_conn)
        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 @@ static void http_server_connection_input(struct connection *_conn)
                }
 
                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_input(struct connection *_conn)
        }
 }
 
+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 @@ http_server_connection_next_response(struct http_server_connection *conn)
                        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_connection_next_response(struct http_server_connection *conn)
        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 @@ static int http_server_connection_send_responses(
        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 @@ int http_server_connection_output(struct http_server_connection *conn)
        } 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 @@ http_server_connection_disconnect(struct http_server_connection *conn,
        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;
        }
 
index dcb47f32132500b2587164ed8df0e545d4ea7b3f..e831aaeb050bb900d4f0b9b77565ecd748ff8f2f 100644 (file)
@@ -61,6 +61,9 @@ struct http_server_response {
        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 @@ struct http_server_connection {
        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 @@ int http_server_response_send_more(struct http_server_response *resp,
 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 @@ http_server_request_version_equals(struct http_server_request *req,
 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,
index bf96c5d17684eb2e7567de1e4152151b5d47e50a..9232a9473ddb27f7367ee4464fa25d33c7d57d7f 100644 (file)
@@ -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 @@ bool http_server_request_unref(struct http_server_request **_req)
 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 @@ void http_server_request_set_destroy_callback(struct http_server_request *req,
        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);
 }
index 3fb70031011a7ce1c0929ae6140e40d5d83c154b..1599a8a84450429fdf3c3d6397845afa23e2bc4a 100644 (file)
@@ -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 @@ void http_server_response_free(struct http_server_response *resp)
 {
        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 @@ void http_server_response_submit_tunnel(struct http_server_response *resp,
 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 @@ int http_server_response_send_more(struct http_server_response *resp,
 
        *error_r = NULL;
 
+       i_assert(!resp->payload_blocking);
        i_assert(resp->payload_input != NULL);
        i_assert(resp->payload_output != NULL);
 
@@ -253,11 +470,14 @@ int http_server_response_send_more(struct http_server_response *resp,
        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 @@ int http_server_response_send_more(struct http_server_response *resp,
        }
 
        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 @@ static int http_server_response_send_real(struct http_server_response *resp,
 {
        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 @@ static int http_server_response_send_real(struct http_server_response *resp,
                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 @@ static int http_server_response_send_real(struct http_server_response *resp,
                ret = -1;
        }
 
-       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 (ret >= 0) {
+               http_server_response_debug(resp, "Sent header");
+
+               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;
 }
index 0b6d48ea9f0e369fa4ad135a0576a0f770fad6f7..e88297e7770192237003aa95985160d1265d1c91 100644 (file)
@@ -168,4 +168,14 @@ void http_server_response_submit_tunnel(struct http_server_response *resp,
 
 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