[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
This commit is contained in:
Glenn Strauss 2016-06-10 00:04:10 -04:00
parent 695c8f4e07
commit f69f209e6d
6 changed files with 236 additions and 136 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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:
*

View File

@ -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 */

View File

@ -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
*

View File

@ -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: