commit aff8d1901084bbfd81f4a6335dbed70a02b930fb from: Omar Polo date: Fri Nov 06 16:09:14 2020 UTC handle CGI concurrently don’t stop-the-world-until-cgi-end, but rather poll on the script, so we can handle other requests in the meantime. commit - 60ba426e7e5da49c017f306be78446032cdaf1cf commit + aff8d1901084bbfd81f4a6335dbed70a02b930fb blob - 2f786bce62e15bbbebfdde9ea693ba0ef1964fd0 blob + ebc4c6242867481af87a37946813cc77ede1392e --- ChangeLog +++ ChangeLog @@ -3,6 +3,9 @@ * gmid.c (url_after_proto): ensure that the requested protocol is “gemini” and not something else that’s long 6 bytes. + * gmid.c (loop): added support for cgi scripts (can handle multiple + concurrently) + 2020-11-06 Omar Polo * gmid.1: added option to log to a file blob - 4c258e917794e087d73fc576648c5e1fbf4661bd blob + 8ab175cccb88092aec202bd08a62cbbc524e091c --- gmid.c +++ gmid.c @@ -68,10 +68,11 @@ struct client { int state; int code; const char *meta; - int fd; + int fd, waiting_on_child; pid_t child; - void *buf, *i; - ssize_t len, off; + char sbuf[1024]; /* static buffer */ + void *buf, *i; /* mmap buffer */ + ssize_t len, off; /* mmap/static buffer */ int af; struct in_addr addr; }; @@ -122,6 +123,8 @@ const char *path_ext(const char*); const char *mime(const char*); int open_file(char*, char*, struct pollfd*, struct client*); void start_cgi(const char*, const char*, struct pollfd*, struct client*); +void cgi_setpoll_on_child(struct pollfd*, struct client*); +void cgi_setpoll_on_client(struct pollfd*, struct client*); void handle_cgi(struct pollfd*, struct client*); void send_file(char*, char*, struct pollfd*, struct client*); void send_dir(char*, struct pollfd*, struct client*); @@ -417,6 +420,8 @@ start_cgi(const char *path, const char *query, close(c->fd); c->fd = p[0]; c->child = pid; + mark_nonblock(c->fd); + c->state = S_SENDING; handle_cgi(fds, c); return; } @@ -430,32 +435,81 @@ err: childerr: dprintf(p[1], "%d internal server error\r\n", TEMP_FAILURE); close(p[1]); + + /* don't call atexit stuff */ _exit(1); } void -handle_cgi(struct pollfd *fds, struct client *c) +cgi_setpoll_on_child(struct pollfd *fds, struct client *c) { - char buf[1024]; - ssize_t r, todo; + int fd; + if (c->waiting_on_child) + return; + c->waiting_on_child = 1; + + fds->events = POLLIN; + + fd = fds->fd; + fds->fd = c->fd; + c->fd = fd; +} + +void +cgi_setpoll_on_client(struct pollfd *fds, struct client *c) +{ + int fd; + + if (!c->waiting_on_child) + return; + c->waiting_on_child = 0; + + fd = fds->fd; + fds->fd = c->fd; + c->fd = fd; +} + +void +handle_cgi(struct pollfd *fds, struct client *c) +{ + ssize_t r; + + /* ensure c->fd is the child and fds->fd the client */ + cgi_setpoll_on_client(fds, c); + while (1) { - r = read(c->fd, buf, sizeof(buf)); - if (r == -1 || r == 0) - break; - todo = r; - while (todo > 0) { - switch (r = tls_write(c->ctx, buf, todo)) { + if (c->len == 0) { + if ((r = read(c->fd, c->sbuf, sizeof(c->sbuf))) == 0) + goto end; + if (r == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + cgi_setpoll_on_child(fds, c); + return; + } + goto end; + } + c->len = r; + c->off = 0; + } + + while (c->len > 0) { + switch (r = tls_write(c->ctx, c->sbuf + c->off, c->len)) { case -1: goto end; case TLS_WANT_POLLOUT: + fds->events = POLLOUT; + return; + case TLS_WANT_POLLIN: - /* evil! */ - continue; + fds->events = POLLIN; + return; default: - todo -= r; + c->off += r; + c->len -= r; + break; } } } @@ -592,7 +646,10 @@ handle(struct pollfd *fds, struct client *client) /* fallthrough */ case S_SENDING: - send_file(NULL, NULL, fds, client); + if (client->child != -1) + handle_cgi(fds, client); + else + send_file(NULL, NULL, fds, client); break; case S_CLOSING: @@ -769,8 +826,12 @@ loop(struct tls *ctx, int sock) err(1, "bad fd %d", fds[i].fd); if (fds[i].revents & POLLHUP) { - goodbye(&fds[i], &clients[i]); - continue; + /* fds[i] may be the fd of the stdin + * of a cgi script that has exited. */ + if (!clients[i].waiting_on_child) { + goodbye(&fds[i], &clients[i]); + continue; + } } todo--;