diff --git a/src/fdevent.c b/src/fdevent.c index 82b71ce6..1e249fbd 100644 --- a/src/fdevent.c +++ b/src/fdevent.c @@ -171,6 +171,30 @@ void fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events) { ev->fdarray[fd]->events = events; } +void fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int event) { + int events; + if (-1 == fd) return; + + events = ev->fdarray[fd]->events; + if ((events & event) || 0 == event) return; /*(no change; nothing to do)*/ + + events |= event; + if (ev->event_set) *fde_ndx = ev->event_set(ev, *fde_ndx, fd, events); + ev->fdarray[fd]->events = events; +} + +void fdevent_event_clr(fdevents *ev, int *fde_ndx, int fd, int event) { + int events; + if (-1 == fd) return; + + events = ev->fdarray[fd]->events; + if (!(events & event)) return; /*(no change; nothing to do)*/ + + events &= ~event; + if (ev->event_set) *fde_ndx = ev->event_set(ev, *fde_ndx, fd, events); + ev->fdarray[fd]->events = events; +} + int fdevent_poll(fdevents *ev, int timeout_ms) { if (ev->poll == NULL) SEGFAULT(); return ev->poll(ev, timeout_ms); diff --git a/src/fdevent.h b/src/fdevent.h index 27a46aaa..361fcb63 100644 --- a/src/fdevent.h +++ b/src/fdevent.h @@ -183,6 +183,8 @@ void fdevent_free(fdevents *ev); #define fdevent_event_get_interest(ev, fd) \ (-1 != (fd) ? (ev)->fdarray[(fd)]->events : 0) void fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events); /* events can be FDEVENT_IN, FDEVENT_OUT or FDEVENT_IN | FDEVENT_OUT */ +void fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int event); /* events can be FDEVENT_IN or FDEVENT_OUT */ +void fdevent_event_clr(fdevents *ev, int *fde_ndx, int fd, int event); /* events can be FDEVENT_IN or FDEVENT_OUT */ void fdevent_event_del(fdevents *ev, int *fde_ndx, int fd); int fdevent_event_get_revent(fdevents *ev, size_t ndx); int fdevent_event_get_fd(fdevents *ev, size_t ndx); diff --git a/src/http_chunk.c b/src/http_chunk.c index d895181f..34d2038f 100644 --- a/src/http_chunk.c +++ b/src/http_chunk.c @@ -133,8 +133,13 @@ static int http_chunk_append_data(server *srv, connection *con, buffer *b, const * file, so not checking if users of this interface have appended large * (references to) files to chunkqueue, which would not be in memory */ + /*(allow slightly larger mem use if FDEVENT_STREAM_RESPONSE_BUFMIN + * to reduce creation of temp files when backend producer will be + * blocked until more data is sent to network to client)*/ + if ((c && c->type == FILE_CHUNK && c->file.is_temp) - || cq->bytes_in - cq->bytes_out + len > 64 * 1024) { + || cq->bytes_in - cq->bytes_out + len + > 1024 * ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) ? 128 : 64)) { return http_chunk_append_to_tempfile(srv, con, b ? b->ptr : mem, len); } diff --git a/src/mod_cgi.c b/src/mod_cgi.c index 1764dd61..456e6f87 100644 --- a/src/mod_cgi.c +++ b/src/mod_cgi.c @@ -400,6 +400,7 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); return FDEVENT_HANDLED_NOT_FINISHED; } /* error */ @@ -550,11 +551,31 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { } con->file_started = 1; + } else { + /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ + if (header_len > MAX_HTTP_REQUEST_HEADER) { + log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); + con->http_status = 502; /* Bad Gateway */ + con->mode = DIRECT; + return FDEVENT_HANDLED_FINISHED; + } } } else { if (0 != http_chunk_append_buffer(srv, con, hctx->response)) { return FDEVENT_HANDLED_ERROR; } + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && chunkqueue_length(con->write_queue) > 65536 - 4096) { + if (!con->is_writable) { + /*(defer removal of FDEVENT_IN interest since + * connection_state_machine() might be able to send data + * immediately, unless !con->is_writable, where + * connection_state_machine() might not loop back to call + * mod_cgi_handle_subrequest())*/ + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } + break; + } } #if 0 @@ -1510,6 +1531,17 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) { if (con->mode != p->id) return HANDLER_GO_ON; if (NULL == hctx) return HANDLER_GO_ON; + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && con->file_started) { + if (chunkqueue_length(con->write_queue) > 65536 - 4096) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) { + /* optimistic read from backend, which might re-enable FDEVENT_IN */ + handler_t rc = cgi_recv_response(srv, hctx); /*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ + } + } + 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)*/ diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c index 5d53ac63..48efd37a 100644 --- a/src/mod_fastcgi.c +++ b/src/mod_fastcgi.c @@ -2511,7 +2511,10 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { * check how much we have to read */ if (ioctl(hctx->fd, FIONREAD, &toread)) { - if (errno == EAGAIN) return 0; + if (errno == EAGAIN) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + return 0; + } log_error_write(srv, __FILE__, __LINE__, "sd", "unexpected end-of-file (perhaps the fastcgi process died):", fcgi_fd); @@ -2522,12 +2525,26 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { char *mem; size_t mem_len; + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)) { + off_t cqlen = chunkqueue_length(hctx->rb); + if (cqlen + toread > 65536 + (int)sizeof(FCGI_Header)) { /*(max size of FastCGI packet + 1)*/ + if (cqlen < 65536 + (int)sizeof(FCGI_Header)) { + toread = 65536 + (int)sizeof(FCGI_Header) - cqlen; + } else { /* should not happen */ + toread = toread < 1024 ? toread : 1024; + } + } + } + chunkqueue_get_memory(hctx->rb, &mem, &mem_len, 0, toread); r = read(hctx->fd, mem, mem_len); chunkqueue_use_memory(hctx->rb, r > 0 ? r : 0); if (-1 == r) { - if (errno == EAGAIN) return 0; + if (errno == EAGAIN) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + return 0; + } log_error_write(srv, __FILE__, __LINE__, "sds", "unexpected end-of-file (perhaps the fastcgi process died):", fcgi_fd, strerror(errno)); @@ -2586,6 +2603,13 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { buffer_string_set_length(hctx->response_header, hlen); } else { /* no luck, no header found */ + /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ + if (buffer_string_length(hctx->response_header) > MAX_HTTP_REQUEST_HEADER) { + log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); + con->http_status = 502; /* Bad Gateway */ + con->mode = DIRECT; + fin = 1; + } break; } @@ -2630,6 +2654,18 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { /* error writing to tempfile; * truncate response or send 500 if nothing sent yet */ fin = 1; + break; + } + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && chunkqueue_length(con->write_queue) > 65536 - 4096) { + if (!con->is_writable) { + /*(defer removal of FDEVENT_IN interest since + * connection_state_machine() might be able to send data + * immediately, unless !con->is_writable, where + * connection_state_machine() might not loop back to call + * mod_fastcgi_handle_subrequest())*/ + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } } } break; @@ -3009,6 +3045,7 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { if (-1 == fcgi_create_env(srv, hctx, hctx->request_id)) return HANDLER_ERROR; + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); fcgi_set_state(srv, hctx, FCGI_STATE_WRITE); /* fall through */ case FCGI_STATE_WRITE: @@ -3040,7 +3077,7 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { } if (hctx->wb->bytes_out == hctx->wb_reqlen) { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); fcgi_set_state(srv, hctx, FCGI_STATE_READ); } else { off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; @@ -3052,9 +3089,9 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { } } if (0 == wblen) { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); } } @@ -3177,6 +3214,17 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && con->file_started) { + if (chunkqueue_length(con->write_queue) > 65536 - 4096) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) { + /* optimistic read from backend, which might re-enable FDEVENT_IN */ + handler_t rc = fcgi_recv_response(srv, hctx); /*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ + } + } + if (0 == hctx->wb->bytes_in ? con->state == CON_STATE_READ_POST : hctx->wb->bytes_in < hctx->wb_reqlen) { diff --git a/src/mod_proxy.c b/src/mod_proxy.c index 9a4982ec..823bb5b2 100644 --- a/src/mod_proxy.c +++ b/src/mod_proxy.c @@ -631,6 +631,10 @@ static int proxy_demux_response(server *srv, handler_ctx *hctx) { /* check how much we have to read */ if (ioctl(hctx->fd, FIONREAD, &b)) { + if (errno == EAGAIN) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + return 0; + } log_error_write(srv, __FILE__, __LINE__, "sd", "ioctl failed: ", proxy_fd); @@ -644,10 +648,29 @@ static int proxy_demux_response(server *srv, handler_ctx *hctx) { } if (b > 0) { + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)) { + off_t cqlen = chunkqueue_length(con->write_queue); + if (cqlen + b > 65536 - 4096) { + if (!con->is_writable) { + /*(defer removal of FDEVENT_IN interest since + * connection_state_machine() might be able to send data + * immediately, unless !con->is_writable, where + * connection_state_machine() might not loop back to call + * mod_proxy_handle_subrequest())*/ + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } + if (cqlen >= 65536-1) return 0; + b = 65536 - 1 - (int)cqlen; + } + } + buffer_string_prepare_append(hctx->response, b); if (-1 == (r = read(hctx->fd, hctx->response->ptr + buffer_string_length(hctx->response), buffer_string_space(hctx->response)))) { - if (errno == EAGAIN) return 0; + if (errno == EAGAIN) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + return 0; + } log_error_write(srv, __FILE__, __LINE__, "sds", "unexpected end-of-file (perhaps the proxy process died):", proxy_fd, strerror(errno)); @@ -701,6 +724,15 @@ static int proxy_demux_response(server *srv, handler_ctx *hctx) { } } buffer_reset(hctx->response); + } else { + /* no luck, no header found */ + /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ + if (buffer_string_length(hctx->response) > MAX_HTTP_REQUEST_HEADER) { + log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); + con->http_status = 502; /* Bad Gateway */ + con->mode = DIRECT; + fin = 1; + } } } else { if (0 != http_chunk_append_buffer(srv, con, hctx->response)) { @@ -802,6 +834,7 @@ static handler_t proxy_write_request(server *srv, handler_ctx *hctx) { case PROXY_STATE_PREPARE_WRITE: proxy_create_env(srv, hctx); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); proxy_set_state(srv, hctx, PROXY_STATE_WRITE); /* fall through */ @@ -821,8 +854,8 @@ static handler_t proxy_write_request(server *srv, handler_ctx *hctx) { } if (hctx->wb->bytes_out == hctx->wb_reqlen) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); proxy_set_state(srv, hctx, PROXY_STATE_READ); - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); } else { off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; if (hctx->wb->bytes_in < hctx->wb_reqlen && wblen < 65536 - 16384) { @@ -833,9 +866,9 @@ static handler_t proxy_write_request(server *srv, handler_ctx *hctx) { } } if (0 == wblen) { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); } } @@ -925,6 +958,17 @@ SUBREQUEST_FUNC(mod_proxy_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && con->file_started) { + if (chunkqueue_length(con->write_queue) > 65536 - 4096) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) { + /* optimistic read from backend, which might re-enable FDEVENT_IN */ + handler_t rc = proxy_recv_response(srv, hctx); /*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ + } + } + if (0 == hctx->wb->bytes_in ? con->state == CON_STATE_READ_POST : hctx->wb->bytes_in < hctx->wb_reqlen) { diff --git a/src/mod_scgi.c b/src/mod_scgi.c index 0c674224..72057f8c 100644 --- a/src/mod_scgi.c +++ b/src/mod_scgi.c @@ -1859,6 +1859,7 @@ static int scgi_demux_response(server *srv, handler_ctx *hctx) { if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); return 0; } /* error */ @@ -1986,6 +1987,14 @@ static int scgi_demux_response(server *srv, handler_ctx *hctx) { } con->file_started = 1; + } else { + /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ + if (buffer_string_length(hctx->response_header) > MAX_HTTP_REQUEST_HEADER) { + log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); + con->http_status = 502; /* Bad Gateway */ + con->mode = DIRECT; + return 1; + } } } else { if (0 != http_chunk_append_buffer(srv, con, hctx->response)) { @@ -1993,6 +2002,18 @@ static int scgi_demux_response(server *srv, handler_ctx *hctx) { * truncate response or send 500 if nothing sent yet */ return 1; } + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && chunkqueue_length(con->write_queue) > 65536 - 4096) { + if (!con->is_writable) { + /*(defer removal of FDEVENT_IN interest since + * connection_state_machine() might be able to send data + * immediately, unless !con->is_writable, where + * connection_state_machine() might not loop back to call + * mod_scgi_handle_subrequest())*/ + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } + break; + } } #if 0 @@ -2370,6 +2391,7 @@ static handler_t scgi_write_request(server *srv, handler_ctx *hctx) { case FCGI_STATE_PREPARE_WRITE: scgi_create_env(srv, hctx); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); scgi_set_state(srv, hctx, FCGI_STATE_WRITE); /* fall through */ @@ -2419,7 +2441,7 @@ static handler_t scgi_write_request(server *srv, handler_ctx *hctx) { } if (hctx->wb->bytes_out == hctx->wb_reqlen) { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); scgi_set_state(srv, hctx, FCGI_STATE_READ); } else { off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; @@ -2431,9 +2453,9 @@ static handler_t scgi_write_request(server *srv, handler_ctx *hctx) { } } if (0 == wblen) { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); } } @@ -2533,6 +2555,17 @@ SUBREQUEST_FUNC(mod_scgi_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && con->file_started) { + if (chunkqueue_length(con->write_queue) > 65536 - 4096) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) { + /* optimistic read from backend, which might re-enable FDEVENT_IN */ + handler_t rc = scgi_recv_response(srv, hctx); /*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ + } + } + if (0 == hctx->wb->bytes_in ? con->state == CON_STATE_READ_POST : hctx->wb->bytes_in < hctx->wb_reqlen) {