Skip to content

Commit 18a7b2b

Browse files
committed
[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
1 parent ddfae01 commit 18a7b2b

File tree

7 files changed

+201
-13
lines changed

7 files changed

+201
-13
lines changed

src/fdevent.c

+24
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,30 @@ void fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events) {
171171
ev->fdarray[fd]->events = events;
172172
}
173173

174+
void fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int event) {
175+
int events;
176+
if (-1 == fd) return;
177+
178+
events = ev->fdarray[fd]->events;
179+
if ((events & event) || 0 == event) return; /*(no change; nothing to do)*/
180+
181+
events |= event;
182+
if (ev->event_set) *fde_ndx = ev->event_set(ev, *fde_ndx, fd, events);
183+
ev->fdarray[fd]->events = events;
184+
}
185+
186+
void fdevent_event_clr(fdevents *ev, int *fde_ndx, int fd, int event) {
187+
int events;
188+
if (-1 == fd) return;
189+
190+
events = ev->fdarray[fd]->events;
191+
if (!(events & event)) return; /*(no change; nothing to do)*/
192+
193+
events &= ~event;
194+
if (ev->event_set) *fde_ndx = ev->event_set(ev, *fde_ndx, fd, events);
195+
ev->fdarray[fd]->events = events;
196+
}
197+
174198
int fdevent_poll(fdevents *ev, int timeout_ms) {
175199
if (ev->poll == NULL) SEGFAULT();
176200
return ev->poll(ev, timeout_ms);

src/fdevent.h

+2
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ void fdevent_free(fdevents *ev);
183183
#define fdevent_event_get_interest(ev, fd) \
184184
(-1 != (fd) ? (ev)->fdarray[(fd)]->events : 0)
185185
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 */
186+
void fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int event); /* events can be FDEVENT_IN or FDEVENT_OUT */
187+
void fdevent_event_clr(fdevents *ev, int *fde_ndx, int fd, int event); /* events can be FDEVENT_IN or FDEVENT_OUT */
186188
void fdevent_event_del(fdevents *ev, int *fde_ndx, int fd);
187189
int fdevent_event_get_revent(fdevents *ev, size_t ndx);
188190
int fdevent_event_get_fd(fdevents *ev, size_t ndx);

src/http_chunk.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,13 @@ static int http_chunk_append_data(server *srv, connection *con, buffer *b, const
133133
* file, so not checking if users of this interface have appended large
134134
* (references to) files to chunkqueue, which would not be in memory */
135135

136+
/*(allow slightly larger mem use if FDEVENT_STREAM_RESPONSE_BUFMIN
137+
* to reduce creation of temp files when backend producer will be
138+
* blocked until more data is sent to network to client)*/
139+
136140
if ((c && c->type == FILE_CHUNK && c->file.is_temp)
137-
|| cq->bytes_in - cq->bytes_out + len > 64 * 1024) {
141+
|| cq->bytes_in - cq->bytes_out + len
142+
> 1024 * ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) ? 128 : 64)) {
138143
return http_chunk_append_to_tempfile(srv, con, b ? b->ptr : mem, len);
139144
}
140145

src/mod_cgi.c

+32
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
400400
if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) {
401401
if (errno == EAGAIN || errno == EINTR) {
402402
/* would block, wait for signal */
403+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
403404
return FDEVENT_HANDLED_NOT_FINISHED;
404405
}
405406
/* error */
@@ -550,11 +551,31 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
550551
}
551552

552553
con->file_started = 1;
554+
} else {
555+
/*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/
556+
if (header_len > MAX_HTTP_REQUEST_HEADER) {
557+
log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path);
558+
con->http_status = 502; /* Bad Gateway */
559+
con->mode = DIRECT;
560+
return FDEVENT_HANDLED_FINISHED;
561+
}
553562
}
554563
} else {
555564
if (0 != http_chunk_append_buffer(srv, con, hctx->response)) {
556565
return FDEVENT_HANDLED_ERROR;
557566
}
567+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
568+
&& chunkqueue_length(con->write_queue) > 65536 - 4096) {
569+
if (!con->is_writable) {
570+
/*(defer removal of FDEVENT_IN interest since
571+
* connection_state_machine() might be able to send data
572+
* immediately, unless !con->is_writable, where
573+
* connection_state_machine() might not loop back to call
574+
* mod_cgi_handle_subrequest())*/
575+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
576+
}
577+
break;
578+
}
558579
}
559580

560581
#if 0
@@ -1510,6 +1531,17 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
15101531
if (con->mode != p->id) return HANDLER_GO_ON;
15111532
if (NULL == hctx) return HANDLER_GO_ON;
15121533

1534+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
1535+
&& con->file_started) {
1536+
if (chunkqueue_length(con->write_queue) > 65536 - 4096) {
1537+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
1538+
} else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) {
1539+
/* optimistic read from backend, which might re-enable FDEVENT_IN */
1540+
handler_t rc = cgi_recv_response(srv, hctx); /*(might invalidate hctx)*/
1541+
if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
1542+
}
1543+
}
1544+
15131545
if (cq->bytes_in != (off_t)con->request.content_length) {
15141546
/*(64k - 4k to attempt to avoid temporary files
15151547
* in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/

src/mod_fastcgi.c

+53-5
Original file line numberDiff line numberDiff line change
@@ -2511,7 +2511,10 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
25112511
* check how much we have to read
25122512
*/
25132513
if (ioctl(hctx->fd, FIONREAD, &toread)) {
2514-
if (errno == EAGAIN) return 0;
2514+
if (errno == EAGAIN) {
2515+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
2516+
return 0;
2517+
}
25152518
log_error_write(srv, __FILE__, __LINE__, "sd",
25162519
"unexpected end-of-file (perhaps the fastcgi process died):",
25172520
fcgi_fd);
@@ -2522,12 +2525,26 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
25222525
char *mem;
25232526
size_t mem_len;
25242527

2528+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)) {
2529+
off_t cqlen = chunkqueue_length(hctx->rb);
2530+
if (cqlen + toread > 65536 + (int)sizeof(FCGI_Header)) { /*(max size of FastCGI packet + 1)*/
2531+
if (cqlen < 65536 + (int)sizeof(FCGI_Header)) {
2532+
toread = 65536 + (int)sizeof(FCGI_Header) - cqlen;
2533+
} else { /* should not happen */
2534+
toread = toread < 1024 ? toread : 1024;
2535+
}
2536+
}
2537+
}
2538+
25252539
chunkqueue_get_memory(hctx->rb, &mem, &mem_len, 0, toread);
25262540
r = read(hctx->fd, mem, mem_len);
25272541
chunkqueue_use_memory(hctx->rb, r > 0 ? r : 0);
25282542

25292543
if (-1 == r) {
2530-
if (errno == EAGAIN) return 0;
2544+
if (errno == EAGAIN) {
2545+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
2546+
return 0;
2547+
}
25312548
log_error_write(srv, __FILE__, __LINE__, "sds",
25322549
"unexpected end-of-file (perhaps the fastcgi process died):",
25332550
fcgi_fd, strerror(errno));
@@ -2586,6 +2603,13 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
25862603
buffer_string_set_length(hctx->response_header, hlen);
25872604
} else {
25882605
/* no luck, no header found */
2606+
/*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/
2607+
if (buffer_string_length(hctx->response_header) > MAX_HTTP_REQUEST_HEADER) {
2608+
log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path);
2609+
con->http_status = 502; /* Bad Gateway */
2610+
con->mode = DIRECT;
2611+
fin = 1;
2612+
}
25892613
break;
25902614
}
25912615

@@ -2630,6 +2654,18 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
26302654
/* error writing to tempfile;
26312655
* truncate response or send 500 if nothing sent yet */
26322656
fin = 1;
2657+
break;
2658+
}
2659+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
2660+
&& chunkqueue_length(con->write_queue) > 65536 - 4096) {
2661+
if (!con->is_writable) {
2662+
/*(defer removal of FDEVENT_IN interest since
2663+
* connection_state_machine() might be able to send data
2664+
* immediately, unless !con->is_writable, where
2665+
* connection_state_machine() might not loop back to call
2666+
* mod_fastcgi_handle_subrequest())*/
2667+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
2668+
}
26332669
}
26342670
}
26352671
break;
@@ -3009,6 +3045,7 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) {
30093045

30103046
if (-1 == fcgi_create_env(srv, hctx, hctx->request_id)) return HANDLER_ERROR;
30113047

3048+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
30123049
fcgi_set_state(srv, hctx, FCGI_STATE_WRITE);
30133050
/* fall through */
30143051
case FCGI_STATE_WRITE:
@@ -3040,7 +3077,7 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) {
30403077
}
30413078

30423079
if (hctx->wb->bytes_out == hctx->wb_reqlen) {
3043-
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
3080+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
30443081
fcgi_set_state(srv, hctx, FCGI_STATE_READ);
30453082
} else {
30463083
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) {
30523089
}
30533090
}
30543091
if (0 == wblen) {
3055-
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
3092+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
30563093
} else {
3057-
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT);
3094+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
30583095
}
30593096
}
30603097

@@ -3177,6 +3214,17 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) {
31773214
/* not my job */
31783215
if (con->mode != p->id) return HANDLER_GO_ON;
31793216

3217+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
3218+
&& con->file_started) {
3219+
if (chunkqueue_length(con->write_queue) > 65536 - 4096) {
3220+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
3221+
} else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) {
3222+
/* optimistic read from backend, which might re-enable FDEVENT_IN */
3223+
handler_t rc = fcgi_recv_response(srv, hctx); /*(might invalidate hctx)*/
3224+
if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
3225+
}
3226+
}
3227+
31803228
if (0 == hctx->wb->bytes_in
31813229
? con->state == CON_STATE_READ_POST
31823230
: hctx->wb->bytes_in < hctx->wb_reqlen) {

src/mod_proxy.c

+48-4
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,10 @@ static int proxy_demux_response(server *srv, handler_ctx *hctx) {
631631

632632
/* check how much we have to read */
633633
if (ioctl(hctx->fd, FIONREAD, &b)) {
634+
if (errno == EAGAIN) {
635+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
636+
return 0;
637+
}
634638
log_error_write(srv, __FILE__, __LINE__, "sd",
635639
"ioctl failed: ",
636640
proxy_fd);
@@ -644,10 +648,29 @@ static int proxy_demux_response(server *srv, handler_ctx *hctx) {
644648
}
645649

646650
if (b > 0) {
651+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)) {
652+
off_t cqlen = chunkqueue_length(con->write_queue);
653+
if (cqlen + b > 65536 - 4096) {
654+
if (!con->is_writable) {
655+
/*(defer removal of FDEVENT_IN interest since
656+
* connection_state_machine() might be able to send data
657+
* immediately, unless !con->is_writable, where
658+
* connection_state_machine() might not loop back to call
659+
* mod_proxy_handle_subrequest())*/
660+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
661+
}
662+
if (cqlen >= 65536-1) return 0;
663+
b = 65536 - 1 - (int)cqlen;
664+
}
665+
}
666+
647667
buffer_string_prepare_append(hctx->response, b);
648668

649669
if (-1 == (r = read(hctx->fd, hctx->response->ptr + buffer_string_length(hctx->response), buffer_string_space(hctx->response)))) {
650-
if (errno == EAGAIN) return 0;
670+
if (errno == EAGAIN) {
671+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
672+
return 0;
673+
}
651674
log_error_write(srv, __FILE__, __LINE__, "sds",
652675
"unexpected end-of-file (perhaps the proxy process died):",
653676
proxy_fd, strerror(errno));
@@ -701,6 +724,15 @@ static int proxy_demux_response(server *srv, handler_ctx *hctx) {
701724
}
702725
}
703726
buffer_reset(hctx->response);
727+
} else {
728+
/* no luck, no header found */
729+
/*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/
730+
if (buffer_string_length(hctx->response) > MAX_HTTP_REQUEST_HEADER) {
731+
log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path);
732+
con->http_status = 502; /* Bad Gateway */
733+
con->mode = DIRECT;
734+
fin = 1;
735+
}
704736
}
705737
} else {
706738
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) {
802834
case PROXY_STATE_PREPARE_WRITE:
803835
proxy_create_env(srv, hctx);
804836

837+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
805838
proxy_set_state(srv, hctx, PROXY_STATE_WRITE);
806839

807840
/* fall through */
@@ -821,8 +854,8 @@ static handler_t proxy_write_request(server *srv, handler_ctx *hctx) {
821854
}
822855

823856
if (hctx->wb->bytes_out == hctx->wb_reqlen) {
857+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
824858
proxy_set_state(srv, hctx, PROXY_STATE_READ);
825-
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
826859
} else {
827860
off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out;
828861
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) {
833866
}
834867
}
835868
if (0 == wblen) {
836-
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
869+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
837870
} else {
838-
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN|FDEVENT_OUT);
871+
fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
839872
}
840873
}
841874

@@ -925,6 +958,17 @@ SUBREQUEST_FUNC(mod_proxy_handle_subrequest) {
925958
/* not my job */
926959
if (con->mode != p->id) return HANDLER_GO_ON;
927960

961+
if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
962+
&& con->file_started) {
963+
if (chunkqueue_length(con->write_queue) > 65536 - 4096) {
964+
fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
965+
} else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) {
966+
/* optimistic read from backend, which might re-enable FDEVENT_IN */
967+
handler_t rc = proxy_recv_response(srv, hctx); /*(might invalidate hctx)*/
968+
if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
969+
}
970+
}
971+
928972
if (0 == hctx->wb->bytes_in
929973
? con->state == CON_STATE_READ_POST
930974
: hctx->wb->bytes_in < hctx->wb_reqlen) {

0 commit comments

Comments
 (0)