changeset 19746:7527051eb56a

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.
author Stephan Bosch <stephan@rename-it.nl>
date Wed, 10 Feb 2016 22:25:07 +0100
parents b445fef19092
children 36963988e4f8
files 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
diffstat 5 files changed, 525 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- 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;
 	}
 
--- 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,
--- 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);
 }
--- 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;
 }
--- 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