[mod_cgi] asynchronous send of request body to CGI

Like other dynamic handler in prior commit,
mod_cgi can read response before sending req body

mod_cgi no longer blocks sending request body to CGI
This commit is contained in:
Glenn Strauss 2016-04-12 00:21:03 -04:00
parent 2f21aaa973
commit 7c0f8a775d
1 changed files with 157 additions and 75 deletions

View File

@ -72,7 +72,9 @@ typedef struct {
typedef struct {
pid_t pid;
int fd;
int fdtocgi;
int fde_ndx; /* index into the fd-event buffer */
int fde_ndx_tocgi; /* index into the fd-event buffer */
connection *remote_conn; /* dumb pointer */
plugin_data *plugin_data; /* dumb pointer */
@ -89,6 +91,7 @@ static handler_ctx * cgi_handler_ctx_init(void) {
hctx->response = buffer_init();
hctx->response_header = buffer_init();
hctx->fd = -1;
hctx->fdtocgi = -1;
return hctx;
}
@ -519,6 +522,17 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
return FDEVENT_HANDLED_NOT_FINISHED;
}
static void cgi_connection_close_fdtocgi(server *srv, handler_ctx *hctx) {
/*(closes only hctx->fdtocgi)*/
fdevent_event_del(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi);
fdevent_unregister(srv->ev, hctx->fdtocgi);
if (close(hctx->fdtocgi)) {
log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", hctx->fdtocgi, strerror(errno));
}
hctx->fdtocgi = -1;
}
static void cgi_connection_close(server *srv, handler_ctx *hctx) {
int status;
pid_t pid;
@ -541,16 +555,16 @@ static void cgi_connection_close(server *srv, handler_ctx *hctx) {
if (close(hctx->fd)) {
log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
}
}
hctx->fd = -1;
hctx->fde_ndx = -1;
if (hctx->fdtocgi != -1) {
cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
}
pid = hctx->pid;
con->plugin_ctx[p->id] = NULL;
/* is this a good idea ? */
cgi_handler_ctx_free(hctx);
/* if waitpid hasn't been called by response.c yet, do it here */
@ -631,6 +645,43 @@ static handler_t cgi_connection_close_callback(server *srv, connection *con, voi
}
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd);
static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) {
handler_ctx *hctx = ctx;
connection *con = hctx->remote_conn;
/*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/
joblist_append(srv, con);
if (revents & FDEVENT_OUT) {
if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) {
cgi_connection_close(srv, hctx);
return HANDLER_ERROR;
}
/* more request body to be sent to CGI */
}
if (revents & FDEVENT_HUP) {
/* skip sending remaining data to CGI */
chunkqueue *cq = con->request_content_queue;
chunkqueue_mark_written(cq, chunkqueue_length(cq));
cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
} else if (revents & FDEVENT_ERR) {
/* kill all connections to the cgi process */
#if 1
log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR");
#endif
cgi_connection_close(srv, hctx);
return HANDLER_ERROR;
}
return HANDLER_FINISHED;
}
static handler_t cgi_handle_fdevent(server *srv, void *ctx, int revents) {
handler_ctx *hctx = ctx;
connection *con = hctx->remote_conn;
@ -724,7 +775,7 @@ static int cgi_env_add(char_array *env, const char *key, size_t key_len, const c
*
* Also always use mmap; the files are "trusted", as we created them.
*/
static int cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) {
static ssize_t cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) {
chunk* const c = cq->first;
off_t offset, toSend, file_end;
ssize_t r;
@ -793,6 +844,89 @@ static int cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunk
chunkqueue_mark_written(cq, r);
}
return r;
}
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) {
connection *con = hctx->remote_conn;
chunkqueue *cq = con->request_content_queue;
chunk *c;
/* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms.
* solution: if this is still a problem on windows, then substitute
* socketpair() for pipe() and closesocket() for close() on windows.
*/
for (c = cq->first; c; c = cq->first) {
ssize_t r = -1;
switch(c->type) {
case FILE_CHUNK:
r = cgi_write_file_chunk_mmap(srv, con, fd, cq);
break;
case MEM_CHUNK:
if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) {
switch(errno) {
case EAGAIN:
case EINTR:
/* ignore and try again */
r = 0;
break;
case EPIPE:
case ECONNRESET:
/* connection closed */
r = -2;
break;
default:
/* fatal error */
log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno));
r = -1;
break;
}
} else if (r > 0) {
chunkqueue_mark_written(cq, r);
}
break;
}
if (0 == r) break; /*(might block)*/
switch (r) {
case -1:
/* fatal error */
return -1;
case -2:
/* connection reset */
log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI");
/* skip all remaining data */
chunkqueue_mark_written(cq, chunkqueue_length(cq));
break;
default:
break;
}
}
if (chunkqueue_is_empty(cq)) {
/* 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 (close(fd)) {
log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno));
}
} else {
cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
}
} else {
/* more request body remains to be sent to CGI so register for fdevents */
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);
fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT);
}
}
return 0;
}
@ -1099,82 +1233,30 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_
close(from_cgi_fds[1]);
close(to_cgi_fds[0]);
if (con->request.content_length) {
chunkqueue *cq = con->request_content_queue;
chunk *c;
assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
/* NOTE: yes, this is synchronous sending of CGI post data;
* if you need something asynchronous (recommended with large
* request bodies), use mod_fastcgi + fcgi-cgi.
*
* Also: windows doesn't support select() on pipes - wouldn't be
* easy to fix for all platforms.
*/
/* there is content to send */
for (c = cq->first; c; c = cq->first) {
int r = -1;
switch(c->type) {
case FILE_CHUNK:
r = cgi_write_file_chunk_mmap(srv, con, to_cgi_fds[1], cq);
break;
case MEM_CHUNK:
if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) {
switch(errno) {
case EAGAIN:
case EINTR:
/* ignore and try again */
r = 0;
break;
case EPIPE:
case ECONNRESET:
/* connection closed */
r = -2;
break;
default:
/* fatal error */
log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno));
r = -1;
break;
}
} else if (r > 0) {
chunkqueue_mark_written(cq, r);
}
break;
}
switch (r) {
case -1:
/* fatal error */
close(from_cgi_fds[0]);
close(to_cgi_fds[1]);
kill(pid, SIGTERM);
cgi_pid_add(srv, p, pid);
return -1;
case -2:
/* connection reset */
log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI");
/* skip all remaining data */
chunkqueue_mark_written(cq, chunkqueue_length(cq));
break;
default:
break;
}
}
}
close(to_cgi_fds[1]);
/* register PID and wait for them asyncronously */
/* register PID and wait for them asynchronously */
hctx->pid = pid;
hctx->fd = from_cgi_fds[0];
hctx->fde_ndx = -1;
if (0 == con->request.content_length) {
close(to_cgi_fds[1]);
} else {
/* there is content to send */
if (-1 == fdevent_fcntl_set(srv->ev, to_cgi_fds[1])) {
log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
close(to_cgi_fds[1]);
cgi_connection_close(srv, hctx);
return -1;
}
if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) {
close(to_cgi_fds[1]);
cgi_connection_close(srv, hctx);
return -1;
}
}
fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);