From f69f209e6d7113f95f10f82c2cf6079fcaca12fe Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Fri, 10 Jun 2016 00:04:10 -0400 Subject: [core] option to stream request body to backend (fixes #376) Set server.stream-request-body = 1 or server.stream-request-body = 2 to have lighttpd connect to backend (CGI, FastCGI, SCGI, proxy) immediately after parsing request headers, and to stream request body as it arrives. default: buffer entire request body before connecting to backend, in order to avoid tying up (limited) backend resources which are often implemented using libraries which wait for entire request body before proceeding. x-ref: "Reimplement upload (POST) handling to match apache/zeus/thttpd/boa functionality" https://redmine.lighttpd.net/issues/376 --- src/connections-glue.c | 8 ++- src/mod_cgi.c | 61 ++++++++++++++++----- src/mod_fastcgi.c | 142 +++++++++++++++++++++++++++++-------------------- src/mod_proxy.c | 63 ++++++++++++++-------- src/mod_scgi.c | 97 ++++++++++++++++++--------------- src/mod_webdav.c | 1 + 6 files changed, 236 insertions(+), 136 deletions(-) diff --git a/src/connections-glue.c b/src/connections-glue.c index 21b28088..3f4b4738 100644 --- a/src/connections-glue.c +++ b/src/connections-glue.c @@ -360,7 +360,9 @@ handler_t connection_handle_read_post_state(server *srv, connection *con) { if (dst_cq->bytes_in == (off_t)con->request.content_length) { /* Content is ready */ con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; - connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + if (con->state == CON_STATE_READ_POST) { + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + } return HANDLER_GO_ON; } else if (is_closed) { #if 0 @@ -374,6 +376,8 @@ handler_t connection_handle_read_post_state(server *srv, connection *con) { return HANDLER_ERROR; } else { con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; - return HANDLER_WAIT_FOR_EVENT; + return (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST) + ? HANDLER_GO_ON + : HANDLER_WAIT_FOR_EVENT; } } diff --git a/src/mod_cgi.c b/src/mod_cgi.c index b55e7366..8f630fa5 100644 --- a/src/mod_cgi.c +++ b/src/mod_cgi.c @@ -697,8 +697,13 @@ static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) { if (revents & FDEVENT_HUP) { /* skip sending remaining data to CGI */ - chunkqueue *cq = con->request_content_queue; - chunkqueue_mark_written(cq, chunkqueue_length(cq)); + if (con->request.content_length) { + chunkqueue *cq = con->request_content_queue; + chunkqueue_mark_written(cq, chunkqueue_length(cq)); + if (cq->bytes_in != (off_t)con->request.content_length) { + con->keep_alive = 0; + } + } cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } else if (revents & FDEVENT_ERR) { @@ -742,10 +747,6 @@ static handler_t cgi_handle_fdevent(server *srv, void *ctx, int revents) { } } - if (revents & FDEVENT_OUT) { - /* nothing to do */ - } - /* perhaps this issue is already handled */ if (revents & FDEVENT_HUP) { /* check if we still have a unfinished header package which is a body in reality */ @@ -960,10 +961,10 @@ static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) { } } - if (chunkqueue_is_empty(cq)) { + if (cq->bytes_out == (off_t)con->request.content_length) { /* sent all request body input */ /* close connection to the cgi-script */ - if (-1 == hctx->fdtocgi) { /*(entire request body sent in initial send to pipe buffer)*/ + if (-1 == hctx->fdtocgi) { /*(received request body sent in initial send to pipe buffer)*/ if (close(fd)) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno)); } @@ -971,11 +972,25 @@ static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) { cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } } else { - /* more request body remains to be sent to CGI so register for fdevents */ + off_t cqlen = cq->bytes_in - cq->bytes_out; + if (cq->bytes_in < (off_t)con->request.content_length && cqlen < 65536 - 16384) { + /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ + if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { + con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; + con->is_readable = 1; /* trigger optimistic read from client */ + } + } if (-1 == hctx->fdtocgi) { /*(not registered yet)*/ hctx->fdtocgi = fd; hctx->fde_ndx_tocgi = -1; fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx); + } + if (0 == cqlen) { /*(chunkqueue_is_empty(cq))*/ + if ((fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT)) { + fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, 0); + } + } else { + /* more request body remains to be sent to CGI so register for fdevents */ fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT); } } @@ -1482,13 +1497,27 @@ TRIGGER_FUNC(cgi_trigger) { SUBREQUEST_FUNC(mod_cgi_handle_subrequest) { plugin_data *p = p_d; handler_ctx *hctx = con->plugin_ctx[p->id]; + chunkqueue *cq = con->request_content_queue; if (con->mode != p->id) return HANDLER_GO_ON; if (NULL == hctx) return HANDLER_GO_ON; - if (con->state == CON_STATE_READ_POST) { - handler_t r = connection_handle_read_post_state(srv, con); - if (r != HANDLER_GO_ON) return r; + if (cq->bytes_in != (off_t)con->request.content_length) { + /*(64k - 4k to attempt to avoid temporary files + * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/ + if (cq->bytes_in - cq->bytes_out > 65536 - 4096 + && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){ + con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; + if (-1 != hctx->fd) return HANDLER_WAIT_FOR_EVENT; + } else { + handler_t r = connection_handle_read_post_state(srv, con); + if (!chunkqueue_is_empty(cq)) { + if (fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT) { + return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r; + } + } + if (r != HANDLER_GO_ON) return r; + } } if (-1 == hctx->fd) { @@ -1500,11 +1529,15 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) { return HANDLER_FINISHED; } - } - #if 0 log_error_write(srv, __FILE__, __LINE__, "sdd", "subrequest, pid =", hctx, hctx->pid); #endif + } else if (!chunkqueue_is_empty(con->request_content_queue)) { + if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) { + cgi_connection_close(srv, hctx); + return HANDLER_ERROR; + } + } /* if not done, wait for CGI to close stdout, so we read EOF on pipe */ return con->file_finished ? HANDLER_FINISHED : HANDLER_WAIT_FOR_EVENT; diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c index 0e5dea1f..874e357b 100644 --- a/src/mod_fastcgi.c +++ b/src/mod_fastcgi.c @@ -338,20 +338,20 @@ typedef struct { fcgi_connection_state_t state; time_t state_timestamp; - int reconnects; /* number of reconnect attempts */ - chunkqueue *rb; /* read queue */ chunkqueue *wb; /* write queue */ + off_t wb_reqlen; buffer *response_header; - size_t request_id; int fd; /* fd to the fastcgi process */ int fde_ndx; /* index into the fd-event buffer */ pid_t pid; int got_proc; + int reconnects; /* number of reconnect attempts */ + int request_id; int send_content_body; plugin_config conf; @@ -497,6 +497,7 @@ static handler_ctx * handler_ctx_init(void) { hctx->rb = chunkqueue_init(); hctx->wb = chunkqueue_init(); + hctx->wb_reqlen = 0; return hctx; } @@ -1712,7 +1713,7 @@ static int fcgi_env_add(buffer *env, const char *key, size_t key_len, const char return 0; } -static int fcgi_header(FCGI_Header * header, unsigned char type, size_t request_id, int contentLength, unsigned char paddingLength) { +static int fcgi_header(FCGI_Header * header, unsigned char type, int request_id, int contentLength, unsigned char paddingLength) { force_assert(contentLength <= FCGI_MAX_LENGTH); header->version = FCGI_VERSION_1; @@ -1895,7 +1896,42 @@ static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_dat return 0; } -static int fcgi_create_env(server *srv, handler_ctx *hctx, size_t request_id) { +static void fcgi_stdin_append(server *srv, connection *con, handler_ctx *hctx, int request_id) { + FCGI_Header header; + chunkqueue *req_cq = con->request_content_queue; + plugin_data *p = hctx->plugin_data; + off_t offset, weWant; + const off_t req_cqlen = req_cq->bytes_in - req_cq->bytes_out; + + /* something to send ? */ + for (offset = 0; offset != req_cqlen; offset += weWant) { + weWant = req_cqlen - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cqlen - offset; + + /* we announce toWrite octets + * now take all request_content chunks available + * */ + + fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0); + chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); + hctx->wb_reqlen += sizeof(header); + + if (p->conf.debug > 10) { + log_error_write(srv, __FILE__, __LINE__, "soso", "tosend:", offset, "/", req_cqlen); + } + + chunkqueue_steal(hctx->wb, req_cq, weWant); + /*(hctx->wb_reqlen already includes content_length)*/ + } + + if (hctx->wb->bytes_in == hctx->wb_reqlen) { + /* terminate STDIN */ + fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); + chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); + hctx->wb_reqlen += (int)sizeof(header); + } +} + +static int fcgi_create_env(server *srv, handler_ctx *hctx, int request_id) { FCGI_BeginRequestRecord beginRecord; FCGI_Header header; @@ -2127,38 +2163,13 @@ static int fcgi_create_env(server *srv, handler_ctx *hctx, size_t request_id) { fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0); buffer_append_string_len(b, (const char *)&header, sizeof(header)); + hctx->wb_reqlen = buffer_string_length(b); chunkqueue_append_buffer(hctx->wb, b); buffer_free(b); } - if (con->request.content_length) { - chunkqueue *req_cq = con->request_content_queue; - off_t offset; - - /* something to send ? */ - for (offset = 0; offset != req_cq->bytes_in; ) { - off_t weWant = req_cq->bytes_in - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cq->bytes_in - offset; - - /* we announce toWrite octets - * now take all the request_content chunks that we need to fill this request - * */ - - fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0); - chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); - - if (p->conf.debug > 10) { - log_error_write(srv, __FILE__, __LINE__, "soso", "tosend:", offset, "/", req_cq->bytes_in); - } - - chunkqueue_steal(hctx->wb, req_cq, weWant); - - offset += weWant; - } - } - - /* terminate STDIN */ - fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); - chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); + hctx->wb_reqlen += con->request.content_length;/* (eventual) (minimal) total request size, not necessarily including all fcgi_headers around content length yet */ + fcgi_stdin_append(srv, con, hctx, request_id); return 0; } @@ -2400,10 +2411,10 @@ range_success: ; typedef struct { buffer *b; - size_t len; + unsigned int len; int type; int padding; - size_t request_id; + int request_id; } fastcgi_response_packet; static int fastcgi_get_packet(server *srv, handler_ctx *hctx, fastcgi_response_packet *packet) { @@ -3028,11 +3039,23 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { } } - if (hctx->wb->bytes_out == hctx->wb->bytes_in) { + if (hctx->wb->bytes_out == hctx->wb_reqlen) { fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); fcgi_set_state(srv, hctx, FCGI_STATE_READ); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; + if (hctx->wb->bytes_in < hctx->wb_reqlen && wblen < 65536 - 16384) { + /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ + if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { + con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; + con->is_readable = 1; /* trigger optimistic read from client */ + } + } + if (0 == wblen) { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + } } return HANDLER_WAIT_FOR_EVENT; @@ -3151,12 +3174,29 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; - if (con->state == CON_STATE_READ_POST) { - handler_t r = connection_handle_read_post_state(srv, con); - if (r != HANDLER_GO_ON) return r; + if (0 == hctx->wb->bytes_in + ? con->state == CON_STATE_READ_POST + : hctx->wb->bytes_in < hctx->wb_reqlen) { + /*(64k - 4k to attempt to avoid temporary files + * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/ + if (hctx->wb->bytes_in - hctx->wb->bytes_out > 65536 - 4096 + && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){ + con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; + if (0 != hctx->wb->bytes_in) return HANDLER_WAIT_FOR_EVENT; + } else { + handler_t r = connection_handle_read_post_state(srv, con); + chunkqueue *req_cq = con->request_content_queue; + if (0 != hctx->wb->bytes_in && !chunkqueue_is_empty(req_cq)) { + fcgi_stdin_append(srv, con, hctx, hctx->request_id); + if (fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_OUT) { + return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r; + } + } + if (r != HANDLER_GO_ON) return r; + } } - return (hctx->state != FCGI_STATE_READ) + return (0 == hctx->wb->bytes_in || !chunkqueue_is_empty(hctx->wb)) ? fcgi_send_request(srv, hctx) : HANDLER_WAIT_FOR_EVENT; } @@ -3171,8 +3211,7 @@ static handler_t fcgi_handle_fdevent(server *srv, void *ctx, int revents) { joblist_append(srv, con); - if ((revents & FDEVENT_IN) && - hctx->state == FCGI_STATE_READ) { + if (revents & FDEVENT_IN) { switch (fcgi_demux_response(srv, hctx)) { case 0: break; @@ -3290,19 +3329,7 @@ static handler_t fcgi_handle_fdevent(server *srv, void *ctx, int revents) { } if (revents & FDEVENT_OUT) { - if (hctx->state == FCGI_STATE_CONNECT_DELAYED || - hctx->state == FCGI_STATE_WRITE) { - /* we are allowed to send something out - * - * 1. in an unfinished connect() call - * 2. in an unfinished write() call (long POST request) - */ - return fcgi_send_request(srv, hctx); /*(might invalidate hctx)*/ - } else { - log_error_write(srv, __FILE__, __LINE__, "sd", - "got a FDEVENT_OUT and didn't know why:", - hctx->state); - } + return fcgi_send_request(srv, hctx); /*(might invalidate hctx)*/ } /* perhaps this issue is already handled */ @@ -3318,7 +3345,8 @@ static handler_t fcgi_handle_fdevent(server *srv, void *ctx, int revents) { * */ fcgi_send_request(srv, hctx); - } else if (hctx->state == FCGI_STATE_READ && + } else if (chunkqueue_is_empty(hctx->wb) && + hctx->wb->bytes_in != 0 && hctx->proc->port == 0) { /* FIXME: * diff --git a/src/mod_proxy.c b/src/mod_proxy.c index 8f3403b6..08465b8e 100644 --- a/src/mod_proxy.c +++ b/src/mod_proxy.c @@ -97,6 +97,7 @@ typedef struct { buffer *response_header; chunkqueue *wb; + off_t wb_reqlen; int fd; /* fd to the proxy process */ int fde_ndx; /* index into the fd-event buffer */ @@ -124,6 +125,7 @@ static handler_ctx * handler_ctx_init(void) { hctx->response_header = buffer_init(); hctx->wb = chunkqueue_init(); + hctx->wb_reqlen = 0; hctx->fd = -1; hctx->fde_ndx = -1; @@ -502,6 +504,7 @@ static int proxy_create_env(server *srv, handler_ctx *hctx) { buffer_append_string_len(b, CONST_STR_LEN("Connection: close\r\n\r\n")); + hctx->wb_reqlen = buffer_string_length(b); chunkqueue_append_buffer(hctx->wb, b); buffer_free(b); @@ -510,7 +513,8 @@ static int proxy_create_env(server *srv, handler_ctx *hctx) { if (con->request.content_length) { chunkqueue *req_cq = con->request_content_queue; - chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in); + chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in); /*(0 == req_cq->bytes_out)*/ + hctx->wb_reqlen += con->request.content_length;/* (eventual) total request size */ } return 0; @@ -816,11 +820,23 @@ static handler_t proxy_write_request(server *srv, handler_ctx *hctx) { return HANDLER_ERROR; } - if (hctx->wb->bytes_out == hctx->wb->bytes_in) { + if (hctx->wb->bytes_out == hctx->wb_reqlen) { proxy_set_state(srv, hctx, PROXY_STATE_READ); fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; + if (hctx->wb->bytes_in < hctx->wb_reqlen && wblen < 65536 - 16384) { + /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ + if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { + con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; + con->is_readable = 1; /* trigger optimistic read from client */ + } + } + if (0 == wblen) { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + } } return HANDLER_WAIT_FOR_EVENT; @@ -905,12 +921,29 @@ SUBREQUEST_FUNC(mod_proxy_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; - if (con->state == CON_STATE_READ_POST) { - handler_t r = connection_handle_read_post_state(srv, con); - if (r != HANDLER_GO_ON) return r; + if (0 == hctx->wb->bytes_in + ? con->state == CON_STATE_READ_POST + : hctx->wb->bytes_in < hctx->wb_reqlen) { + /*(64k - 4k to attempt to avoid temporary files + * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/ + if (hctx->wb->bytes_in - hctx->wb->bytes_out > 65536 - 4096 + && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){ + con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; + if (0 != hctx->wb->bytes_in) return HANDLER_WAIT_FOR_EVENT; + } else { + handler_t r = connection_handle_read_post_state(srv, con); + chunkqueue *req_cq = con->request_content_queue; + if (0 != hctx->wb->bytes_in && !chunkqueue_is_empty(req_cq)) { + chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in - req_cq->bytes_out); + if (fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_OUT) { + return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r; + } + } + if (r != HANDLER_GO_ON) return r; + } } - return (hctx->state != PROXY_STATE_READ) + return (0 == hctx->wb->bytes_in || !chunkqueue_is_empty(hctx->wb)) ? proxy_send_request(srv, hctx) : HANDLER_WAIT_FOR_EVENT; } @@ -922,8 +955,7 @@ static handler_t proxy_handle_fdevent(server *srv, void *ctx, int revents) { joblist_append(srv, con); - if ((revents & FDEVENT_IN) && - hctx->state == PROXY_STATE_READ) { + if (revents & FDEVENT_IN) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sd", @@ -985,18 +1017,7 @@ static handler_t proxy_handle_fdevent(server *srv, void *ctx, int revents) { proxy_set_state(srv, hctx, PROXY_STATE_PREPARE_WRITE); } - if (hctx->state == PROXY_STATE_PREPARE_WRITE || - hctx->state == PROXY_STATE_WRITE) { - /* we are allowed to send something out - * - * 1. after a just finished connect() call - * 2. in a unfinished write() call (long POST request) - */ - return proxy_send_request(srv, hctx); /*(might invalidate hctx)*/ - } else { - log_error_write(srv, __FILE__, __LINE__, "sd", - "proxy: out", hctx->state); - } + return proxy_send_request(srv, hctx); /*(might invalidate hctx)*/ } /* perhaps this issue is already handled */ diff --git a/src/mod_scgi.c b/src/mod_scgi.c index 3e7cd10b..5bd8714d 100644 --- a/src/mod_scgi.c +++ b/src/mod_scgi.c @@ -304,9 +304,6 @@ typedef enum { FCGI_STATE_INIT, FCGI_STATE_CONNECT, FCGI_STATE_PREPARE_WRITE, typedef struct { buffer *response; - size_t response_len; - int response_type; - int response_padding; scgi_proc *proc; scgi_extension_host *host; @@ -314,20 +311,17 @@ typedef struct { scgi_connection_state_t state; time_t state_timestamp; - int reconnects; /* number of reconnect attempts */ - chunkqueue *wb; + off_t wb_reqlen; buffer *response_header; - int delayed; /* flag to mark that the connect() is delayed */ - - size_t request_id; int fd; /* fd to the scgi process */ int fde_ndx; /* index into the fd-event buffer */ pid_t pid; int got_proc; + int reconnects; /* number of reconnect attempts */ plugin_config conf; @@ -367,18 +361,15 @@ static handler_ctx * handler_ctx_init(void) { hctx->response = buffer_init(); hctx->response_header = buffer_init(); - hctx->request_id = 0; hctx->state = FCGI_STATE_INIT; hctx->proc = NULL; - hctx->response_len = 0; - hctx->response_type = 0; - hctx->response_padding = 0; hctx->fd = -1; hctx->reconnects = 0; hctx->wb = chunkqueue_init(); + hctx->wb_reqlen = 0; return hctx; } @@ -1371,7 +1362,6 @@ static int scgi_reconnect(server *srv, handler_ctx *hctx) { scgi_set_state(srv, hctx, FCGI_STATE_INIT); - hctx->request_id = 0; hctx->reconnects++; if (p->conf.debug) { @@ -1736,13 +1726,15 @@ static int scgi_create_env(server *srv, handler_ctx *hctx) { buffer_append_string_buffer(b, p->scgi_env); buffer_append_string_len(b, CONST_STR_LEN(",")); + hctx->wb_reqlen = buffer_string_length(b); chunkqueue_append_buffer(hctx->wb, b); buffer_free(b); if (con->request.content_length) { chunkqueue *req_cq = con->request_content_queue; - chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in); + chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in); /*(0 == req_cq->bytes_out)*/ + hctx->wb_reqlen += con->request.content_length;/* (eventual) total request size */ } return 0; @@ -2426,11 +2418,23 @@ static handler_t scgi_write_request(server *srv, handler_ctx *hctx) { } } - if (hctx->wb->bytes_out == hctx->wb->bytes_in) { + if (hctx->wb->bytes_out == hctx->wb_reqlen) { fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); scgi_set_state(srv, hctx, FCGI_STATE_READ); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; + if (hctx->wb->bytes_in < hctx->wb_reqlen && wblen < 65536 - 16384) { + /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ + if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { + con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; + con->is_readable = 1; /* trigger optimistic read from client */ + } + } + if (0 == wblen) { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + } } return HANDLER_WAIT_FOR_EVENT; @@ -2525,12 +2529,29 @@ SUBREQUEST_FUNC(mod_scgi_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; - if (con->state == CON_STATE_READ_POST) { - handler_t r = connection_handle_read_post_state(srv, con); - if (r != HANDLER_GO_ON) return r; + if (0 == hctx->wb->bytes_in + ? con->state == CON_STATE_READ_POST + : hctx->wb->bytes_in < hctx->wb_reqlen) { + /*(64k - 4k to attempt to avoid temporary files + * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/ + if (hctx->wb->bytes_in - hctx->wb->bytes_out > 65536 - 4096 + && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){ + con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; + if (0 != hctx->wb->bytes_in) return HANDLER_WAIT_FOR_EVENT; + } else { + handler_t r = connection_handle_read_post_state(srv, con); + chunkqueue *req_cq = con->request_content_queue; + if (0 != hctx->wb->bytes_in && !chunkqueue_is_empty(req_cq)) { + chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in - req_cq->bytes_out); + if (fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_OUT) { + return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r; + } + } + if (r != HANDLER_GO_ON) return r; + } } - return (hctx->state != FCGI_STATE_READ) + return (0 == hctx->wb->bytes_in || !chunkqueue_is_empty(hctx->wb)) ? scgi_send_request(srv, hctx) : HANDLER_WAIT_FOR_EVENT; } @@ -2546,8 +2567,7 @@ static handler_t scgi_handle_fdevent(server *srv, void *ctx, int revents) { joblist_append(srv, con); - if ((revents & FDEVENT_IN) && - hctx->state == FCGI_STATE_READ) { + if (revents & FDEVENT_IN) { switch (scgi_demux_response(srv, hctx)) { case 0: break; @@ -2643,19 +2663,7 @@ static handler_t scgi_handle_fdevent(server *srv, void *ctx, int revents) { } if (revents & FDEVENT_OUT) { - if (hctx->state == FCGI_STATE_CONNECT || - hctx->state == FCGI_STATE_WRITE) { - /* we are allowed to send something out - * - * 1. in a unfinished connect() call - * 2. in a unfinished write() call (long POST request) - */ - return scgi_send_request(srv, hctx); /*(might invalidate hctx)*/ - } else { - log_error_write(srv, __FILE__, __LINE__, "sd", - "got a FDEVENT_OUT and didn't know why:", - hctx->state); - } + return scgi_send_request(srv, hctx); /*(might invalidate hctx)*/ } /* perhaps this issue is already handled */ @@ -2671,13 +2679,6 @@ static handler_t scgi_handle_fdevent(server *srv, void *ctx, int revents) { * */ scgi_send_request(srv, hctx); - } else if (hctx->state == FCGI_STATE_READ && - hctx->proc->port == 0) { - /* FIXME: - * - * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket - * even if the FCGI_FIN packet is not received yet - */ } else { log_error_write(srv, __FILE__, __LINE__, "sbSBSDSd", "error: unexpected close of scgi connection for", @@ -2827,6 +2828,18 @@ static handler_t scgi_check_extension(server *srv, connection *con, void *p_d, i /* a note about no handler is not sent yet */ extension->note_is_sent = 0; + /* SCGI requires that Content-Length be set. + * Send 411 Length Required if Content-Length missing. + * (Alternatively, collect full request body before proceeding + * in mod_scgi_handle_subrequest()) */ + if (0 == con->request.content_length + && array_get_element(con->request.headers, "Transfer-Encoding")) { + con->keep_alive = 0; + con->http_status = 411; /* Length Required */ + con->mode = DIRECT; + return HANDLER_FINISHED; + } + /* * if check-local is disabled, use the uri.path handler * diff --git a/src/mod_webdav.c b/src/mod_webdav.c index 94bd85f6..1b51a71f 100644 --- a/src/mod_webdav.c +++ b/src/mod_webdav.c @@ -2736,6 +2736,7 @@ PHYSICALPATH_FUNC(mod_webdav_physical_handler) { case HTTP_METHOD_DELETE: case HTTP_METHOD_LOCK: case HTTP_METHOD_UNLOCK: + con->conf.stream_request_body = 0; con->mode = p->id; break; default: -- cgit v1.2.1