It allows sending response payload in several chunks in a blocking fashion.
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);
}
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);
}
{
struct istream *payload;
+ i_assert(!conn->in_req_callback);
i_assert(conn->incoming_payload == NULL);
if (req->req.version_major != 1) {
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 */
}
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;
}
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 =
bool cont;
int ret;
+ i_assert(!conn->in_req_callback);
i_assert(!conn->input_broken && conn->incoming_payload == NULL);
i_assert(!conn->close_indicated);
}
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);
}
}
+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)
{
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;
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;
}
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)
} 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;
}
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;
}
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;
};
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 {
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);
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,
/* 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"
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) {
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);
}
#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
*/
{
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)
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)
{
*error_r = NULL;
+ i_assert(!resp->payload_blocking);
i_assert(resp->payload_input != NULL);
i_assert(resp->payload_output != NULL);
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",
}
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;
{
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];
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")) {
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;
}
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