Browse Source

[core] option to stream response body to client (fixes #949, #760, #1283, #1387)

Set server.stream-response-body = 1 or server.stream-response-body = 2
to have lighttpd stream response body to client as it arrives from the
backend (CGI, FastCGI, SCGI, proxy).

default: buffer entire response body before sending response to client.
(This preserves existing behavior for now, but may in the future be
 changed to stream response to client, which is the behavior more
 commonly expected.)

x-ref:
  "fastcgi, cgi, flush, php5 problem."
  https://redmine.lighttpd.net/issues/949
  "Random crashing on FreeBSD 6.1"
  https://redmine.lighttpd.net/issues/760
  "Memory usage increases when proxy+ssl+large file"
  https://redmine.lighttpd.net/issues/1283
  "lighttpd+fastcgi memory problem"
  https://redmine.lighttpd.net/issues/1387
personal/stbuehler/mod-csrf-old
Glenn Strauss 5 years ago
parent
commit
18a7b2be37
  1. 24
      src/fdevent.c
  2. 2
      src/fdevent.h
  3. 7
      src/http_chunk.c
  4. 32
      src/mod_cgi.c
  5. 58
      src/mod_fastcgi.c
  6. 52
      src/mod_proxy.c
  7. 39
      src/mod_scgi.c

24
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);

2
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);

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

32
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)*/

58
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) {

52
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) {

39
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) {

Loading…
Cancel
Save