I'm not sure httpd(8) handles correctly when the fastcgi application
(e.g. slowcgi) closes the connection prematurely.
To verify it, I'm playing with three simple CGI scripts running under
slowcgi with a very low timeout (-t2). The scripts test "hangs"
(which slowcgi turns into hard disconnections) in different places:
/* slow.c -- hang before replying */
sleep(10);
puts("Content-Type: text/plain\n");
puts("hello, world");
return 0;
/* slow2.c -- hang during headers */
puts("Content-Type: text/plain");
fflush(stdout);
sleep(10);
puts("");
puts("hello, world");
return 0;
/* slow3.c -- hang during the body */
puts("Content-Type: text/plain\n");
printf("hello, (wait for it...)");
fflush(stdout);
sleep(10);
puts("world!");
return 0;
Those sleep calls are meant to hit slowcgi_timeout which abruptly
closes the connection with httpd(8).
httpd handles the fastcgi termination via server_file_error which
assumes the request' headers and body were already sent and so goes on
to set up for the next request (if clt_persist.) This leaves the HTTP
client hanging and waiting for a response that won't arrive, until
they give up when the fastcgi reply wasn't complete.
Diff below (first three hunks) addresses this issue by introducing a
server_fcgi_error callback that handles the "no header" and
"incomplete data" cases before calling server_file_error. With it,
the first two scripts become an httpd' "500 error" page and the third
is correctly closed after the first line.
I'm also bundling another small change for slowcgi to make it send a
FCGI_END_REQUEST record instead of abruptly closing the request. I
think it's better to let the fastcgi server know that the request is
over, but the slowcgi/httpd combo works even without it.
diff refs/heads/wip refs/heads/fastcgi
commit - 3aac3f8529325cbd58806247542caabd23befb6e
commit + cab6b39fe822d105a2dc852f2435693a6eff834f
blob - 381fade2924c4b5cea77cd9cd6500e75d4d59257
blob + b6541b7c68235ac1dfc5a9d0243db988e5932a7f
--- usr.sbin/httpd/server_fcgi.c
+++ usr.sbin/httpd/server_fcgi.c
@@ -77,6 +77,7 @@ struct server_fcgi_param {
};
int server_fcgi_header(struct client *, unsigned int);
+void server_fcgi_error(struct bufferevent *, short, void *);
void server_fcgi_read(struct bufferevent *, void *);
int server_fcgi_writeheader(struct client *, struct kv *, void *);
int server_fcgi_writechunk(struct client *);
@@ -133,7 +134,7 @@ server_fcgi(struct httpd *env, struct client *clt)
clt->clt_srvbev_throttled = 0;
clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read,
- NULL, server_file_error, clt);
+ NULL, server_fcgi_error, clt);
if (clt->clt_srvbev == NULL) {
errstr = "failed to allocate fcgi buffer event";
goto fail;
@@ -482,6 +483,23 @@ fcgi_add_param(struct server_fcgi_param *p, const char
}
void
+server_fcgi_error(struct bufferevent *bev, short error, void *arg)
+{
+ struct client *clt = arg;
+
+ if ((error & EVBUFFER_EOF) && !clt->clt_fcgi.headersdone) {
+ server_abort_http(clt, 500, "malformed or no headers");
+ return;
+ }
+
+ /* send the end marker if not already */
+ if (clt->clt_fcgi.chunked && !clt->clt_fcgi.end++)
+ server_bufferevent_print(clt, "0\r\n\r\n");
+
+ server_file_error(bev, error, arg);
+}
+
+void
server_fcgi_read(struct bufferevent *bev, void *arg)
{
uint8_t buf[FCGI_RECORD_SIZE];
blob - ddf83f965d0e6a99ada695694bea77b775bae2aa
blob + 1d577ba63efca388ca3644d1a52d9b3d9f246014
--- usr.sbin/slowcgi/slowcgi.c
+++ usr.sbin/slowcgi/slowcgi.c
@@ -515,7 +515,34 @@ slowcgi_accept(int fd, short events, void *arg)
void
slowcgi_timeout(int fd, short events, void *arg)
{
- cleanup_request((struct request*) arg);
+ struct request *c;
+
+ c = arg;
+
+ if (c->script_flags & (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE))
+ return;
+
+ if (!c->stdout_fd_closed) {
+ c->stdout_fd_closed = 1;
+ close(EVENT_FD(&c->script_ev));
+ event_del(&c->script_ev);
+ }
+
+ if (!c->stderr_fd_closed) {
+ c->stderr_fd_closed = 1;
+ close(EVENT_FD(&c->script_err_ev));
+ event_del(&c->script_err_ev);
+ }
+
+ if (!c->stdin_fd_closed) {
+ c->stdin_fd_closed = 1;
+ close(EVENT_FD(&c->script_stdin_ev));
+ event_del(&c->script_stdin_ev);
+ }
+
+ c->script_status = SIGALRM;
+ c->script_flags = STDOUT_DONE | STDERR_DONE | SCRIPT_DONE;
+ create_end_record(c);
}
void