Received: (from majordom@localhost) by hyperreal.org (8.8.5/8.8.5) id SAA15670; Sat, 19 Jul 1997 18:04:06 -0700 (PDT) Received: from twinlark.arctic.org (twinlark.arctic.org [204.62.130.91]) by hyperreal.org (8.8.5/8.8.5) with SMTP id SAA15665 for ; Sat, 19 Jul 1997 18:04:03 -0700 (PDT) Received: (qmail 2653 invoked from network); 20 Jul 1997 01:03:59 -0000 Received: from benchlark.arctic.org (206.221.201.235) by twinlark.arctic.org with SMTP; 20 Jul 1997 01:03:59 -0000 Date: Sat, 19 Jul 1997 18:03:07 -0700 (PDT) From: Dean Gaudet To: new-httpd@apache.org Subject: [PATCH] nph killer, cgi buffering Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Sender: new-httpd-owner@apache.org Precedence: bulk Reply-To: new-httpd@apache.org This patch: - provides BUFF * interfaces to the various things cgi needs, in a way that avoids mucho code duplication - adds bnonblock() to set a BUFF to non-blocking mode, and bfileno() to get an fd for calling select() on. - provides send_fb/send_fb_length which do non-blocking reads, and whenever a read would block they bflush the client before blocking. - eliminates the special case "nph-" for CGIs. (nph doesn't work with HTTP/1.1 or SSL) - works on simple .cgis, needs testing via mod_include, needs more eyes to look at it. Needs testing on NT (needs porting??). Dean Index: alloc.c =================================================================== RCS file: /export/home/cvs/apache/src/alloc.c,v retrieving revision 1.40 diff -u -r1.40 alloc.c --- alloc.c 1997/07/15 21:39:50 1.40 +++ alloc.c 1997/07/20 00:56:14 @@ -1064,9 +1064,16 @@ #define enc_pipe(fds) pipe(fds) #endif /* WIN32 */ -API_EXPORT(int) spawn_child_err (pool *p, int (*func)(void *), void *data, +/* for fdopen, to get binary mode */ +#if defined (__EMX__) || defined (WIN32) +#define BINMODE "b" +#else +#define BINMODE +#endif + +static int spawn_child_err_core (pool *p, int (*func)(void *), void *data, enum kill_conditions kill_how, - FILE **pipe_in, FILE **pipe_out, FILE **pipe_err) + int *pipe_in, int *pipe_out, int *pipe_err) { int pid; int in_fds[2]; @@ -1074,13 +1081,7 @@ int err_fds[2]; int save_errno; - block_alarms(); - - if (pipe_in && enc_pipe (in_fds) < 0) - { - save_errno = errno; - unblock_alarms(); - errno = save_errno; + if (pipe_in && enc_pipe (in_fds) < 0) { return 0; } @@ -1089,7 +1090,6 @@ if (pipe_in) { close (in_fds[0]); close (in_fds[1]); } - unblock_alarms(); errno = save_errno; return 0; } @@ -1102,7 +1102,6 @@ if (pipe_out) { close (out_fds[0]); close (out_fds[1]); } - unblock_alarms(); errno = save_errno; return 0; } @@ -1158,23 +1157,14 @@ if(pid) { note_subprocess(p, pid, kill_how); - if(pipe_in) - { - *pipe_in = fdopen(in_fds[1], "wb"); - if(*pipe_in) - note_cleanups_for_file(p, *pipe_in); + if(pipe_in) { + *pipe_in = in_fds[1]; } - if(pipe_out) - { - *pipe_out = fdopen(out_fds[0], "rb"); - if(*pipe_out) - note_cleanups_for_file(p, *pipe_out); + if(pipe_out) { + *pipe_out = out_fds[0]; } - if(pipe_err) - { - *pipe_err = fdopen(err_fds[0], "rb"); - if(*pipe_err) - note_cleanups_for_file(p, *pipe_err); + if(pipe_err) { + *pipe_err = err_fds[0]; } } SetThreadPriority(thread_handle, old_priority); @@ -1198,7 +1188,6 @@ if (pipe_err) { close (err_fds[0]); close (err_fds[1]); } - unblock_alarms(); errno = save_errno; return 0; } @@ -1237,43 +1226,106 @@ if (pipe_out) { close (out_fds[1]); -#ifdef __EMX__ - /* Need binary mode set for OS/2. */ - *pipe_out = fdopen (out_fds[0], "rb"); -#else - *pipe_out = fdopen (out_fds[0], "r"); -#endif - - if (*pipe_out) note_cleanups_for_file (p, *pipe_out); + *pipe_out = out_fds[0]; } if (pipe_in) { close (in_fds[0]); -#ifdef __EMX__ - /* Need binary mode set for OS/2 */ - *pipe_in = fdopen (in_fds[1], "wb"); -#else - *pipe_in = fdopen (in_fds[1], "w"); -#endif - - if (*pipe_in) note_cleanups_for_file (p, *pipe_in); + *pipe_in = in_fds[1]; } if (pipe_err) { close (err_fds[1]); -#ifdef __EMX__ - /* Need binary mode set for OS/2. */ - *pipe_err = fdopen (err_fds[0], "rb"); -#else - *pipe_err = fdopen (err_fds[0], "r"); -#endif - - if (*pipe_err) note_cleanups_for_file (p, *pipe_err); + *pipe_err = err_fds[0]; } #endif /* WIN32 */ - unblock_alarms(); return pid; +} + + +API_EXPORT(int) spawn_child_err (pool *p, int (*func)(void *), void *data, + enum kill_conditions kill_how, + FILE **pipe_in, FILE **pipe_out, FILE **pipe_err) +{ + int fd_in, fd_out, fd_err; + int pid, save_errno; + + block_alarms(); + + pid = spawn_child_err_core (p, func, data, kill_how, + pipe_in ? &fd_in : NULL, + pipe_out ? &fd_out : NULL, + pipe_err ? &fd_err : NULL ); + + if (pid == 0) { + save_errno = errno; + unblock_alarms(); + errno = save_errno; + return 0; + } + + if (pipe_out) { + *pipe_out = fdopen (fd_out, "r" BINMODE); + if (*pipe_out) note_cleanups_for_file (p, *pipe_out); + else close (fd_out); + } + + if (pipe_in) { + *pipe_in = fdopen (fd_in, "w" BINMODE); + if (*pipe_in) note_cleanups_for_file (p, *pipe_in); + else close (fd_in); + } + + if (pipe_err) { + *pipe_err = fdopen (fd_err, "r" BINMODE); + if (*pipe_err) note_cleanups_for_file (p, *pipe_err); + else close (fd_err); + } + + unblock_alarms(); + return pid; +} + + +API_EXPORT(int) spawn_child_err_buff (pool *p, int (*func)(void *), void *data, + enum kill_conditions kill_how, + BUFF **pipe_in, BUFF **pipe_out, BUFF **pipe_err) +{ + int fd_in, fd_out, fd_err; + int pid, save_errno; + + block_alarms(); + + pid = spawn_child_err_core (p, func, data, kill_how, + pipe_in ? &fd_in : NULL, + pipe_out ? &fd_out : NULL, + pipe_err ? &fd_err : NULL ); + + if (pid == 0) { + save_errno = errno; + unblock_alarms(); + errno = save_errno; + return 0; + } + + if (pipe_out) { + *pipe_out = bcreate(p, B_RD); + bpushfd(*pipe_out, fd_out, fd_out); + } + + if (pipe_in) { + *pipe_in = bcreate(p, B_WR); + bpushfd(*pipe_in, fd_in, fd_in); + } + + if (pipe_err) { + *pipe_err = bcreate(p, B_RD); + bpushfd(*pipe_err, fd_err, fd_err); + } + + unblock_alarms(); + return pid; } static void free_proc_chain (struct process_chain *procs) Index: buff.c =================================================================== RCS file: /export/home/cvs/apache/src/buff.c,v retrieving revision 1.37 diff -u -r1.37 buff.c --- buff.c 1997/07/15 21:39:50 1.37 +++ buff.c 1997/07/20 00:56:16 @@ -415,18 +415,38 @@ { if (value) { fb->flags |= flag; - if( flag & B_CHUNK ) { + if (flag & B_CHUNK) { start_chunk(fb); } } else { fb->flags &= ~flag; - if( flag & B_CHUNK ) { + if (flag & B_CHUNK) { end_chunk(fb); } } return value; } + +API_EXPORT(int) bnonblock(BUFF *fb, int direction) +{ + int fd; + + fd = ( direction == B_RD ) ? fb->fd_in : fb->fd; +#if defined(O_NONBLOCK) + return fcntl (fd, F_SETFL, O_NONBLOCK); +#elif defined(F_NDELAY) + return fcntl (fd, F_SETFL, F_NDELAY); +#else + return 0; +#endif +} + +API_EXPORT(int) bfileno(BUFF *fb, int direction) +{ + return (direction == B_RD) ? fb->fd_in : fb->fd; +} + /* * This is called instead of read() everywhere in here. It implements * the B_SAFEREAD functionality -- which is to force a flush() if a read() @@ -521,10 +541,18 @@ if (fb->flags & B_RDERR) return -1; if (nbyte == 0) return 0; - if (!(fb->flags & B_RD)) - { -/* Unbuffered reading */ + if (!(fb->flags & B_RD)) { + /* Unbuffered reading. First check if there was something in the + * buffer from before we went unbuffered. */ + if (fb->incnt) { + i = (fb->incnt > nbyte) ? nbyte : fb->incnt; + memcpy (buf, fb->inptr, i); + fb->incnt -= i; + fb->inptr += i; + return i; + } i = saferead( fb, buf, nbyte ); + if (i == 0) fb->flags |= B_EOF; if (i == -1 && errno != EAGAIN) doerror(fb, B_RD); return i; } Index: buff.h =================================================================== RCS file: /export/home/cvs/apache/src/buff.h,v retrieving revision 1.21 diff -u -r1.21 buff.h --- buff.h 1997/07/19 22:34:05 1.21 +++ buff.h 1997/07/20 00:56:17 @@ -158,3 +158,12 @@ #define bputc(c, fb) ((((fb)->flags & (B_EOUT|B_WRERR|B_WR)) != B_WR || \ (fb)->outcnt == (fb)->bufsiz) ? bflsbuf(c, (fb)) : \ ((fb)->outbase[(fb)->outcnt++] = (c), 0)) + +API_EXPORT(int) spawn_child_err_buff (pool *, int (*)(void *), void *, + enum kill_conditions, BUFF **pipe_in, BUFF **pipe_out, + BUFF **pipe_err); + +/* enable non-blocking operations */ +API_EXPORT(int) bnonblock(BUFF *fb, int direction); +/* and get an fd to select() on */ +API_EXPORT(int) bfileno(BUFF *fb, int direction); Index: http_protocol.c =================================================================== RCS file: /export/home/cvs/apache/src/http_protocol.c,v retrieving revision 1.143 diff -u -r1.143 http_protocol.c --- http_protocol.c 1997/07/19 20:27:52 1.143 +++ http_protocol.c 1997/07/20 00:56:22 @@ -1626,6 +1626,85 @@ return total_bytes_sent; } +/* + * Send the body of a response to the client. + */ +API_EXPORT(long) send_fb(BUFF *fb, request_rec *r) { + return send_fb_length(fb, r, -1); +} + +API_EXPORT(long) send_fb_length(BUFF *fb, request_rec *r, long length) +{ + char buf[IOBUFSIZE]; + long total_bytes_sent = 0; + register int n, w, o, len, fd; + fd_set fds; + + if (length == 0) return 0; + + /* Make fb unbuffered and non-blocking */ + bsetflag (fb, B_RD, 0); + bnonblock (fb, B_RD); + fd = bfileno (fb, B_RD); + + soft_timeout("send body", r); + + while (!r->connection->aborted) { + if ((length > 0) && (total_bytes_sent + IOBUFSIZE) > length) + len = length - total_bytes_sent; + else len = IOBUFSIZE; + + do { + n = bread (fb, buf, len); + if (n >= 0) break; + if (n < 0 && errno != EAGAIN) break; + /* we need to block, so flush the output first */ + bflush (r->connection->client); + FD_ZERO (&fds); + FD_SET (fd, &fds); + /* we don't care what select says, we might as well loop back + * around and try another read + */ + ap_select (fd+1, &fds, NULL, NULL, NULL); + } while (!r->connection->aborted); + + if (n < 1 || r->connection->aborted) { + break; + } + + o=0; + total_bytes_sent += n; + + while (n && !r->connection->aborted) { + w = bwrite(r->connection->client, &buf[o], n); + if (w > 0) { + reset_timeout(r); /* reset timeout after successful write */ + n-=w; + o+=w; + } + else if (w < 0) { + if (r->connection->aborted) + break; + else if (errno == EAGAIN) + continue; + else { + log_unixerr("send body lost connection to", + get_remote_host(r->connection, + r->per_dir_config, REMOTE_NAME), + NULL, r->server); + bsetflag(r->connection->client, B_EOUT, 1); + r->connection->aborted = 1; + break; + } + } + } + } + + kill_timeout(r); + SET_BYTES_SENT(r); + return total_bytes_sent; +} + API_EXPORT(int) rputc (int c, request_rec *r) { if (r->connection->aborted) return EOF; Index: http_protocol.h =================================================================== RCS file: /export/home/cvs/apache/src/http_protocol.h,v retrieving revision 1.24 diff -u -r1.24 http_protocol.h --- http_protocol.h 1997/07/19 20:27:52 1.24 +++ http_protocol.h 1997/07/20 00:56:23 @@ -110,6 +110,9 @@ API_EXPORT(long) send_fd(FILE *f, request_rec *r); API_EXPORT(long) send_fd_length(FILE *f, request_rec *r, long length); + +API_EXPORT(long) send_fb(BUFF *f, request_rec *r); +API_EXPORT(long) send_fb_length(BUFF *f, request_rec *r, long length); /* Hmmm... could macrofy these for now, and maybe forever, though the * definitions of the macros would get a whole lot hairier. Index: mod_cgi.c =================================================================== RCS file: /export/home/cvs/apache/src/mod_cgi.c,v retrieving revision 1.49 diff -u -r1.49 mod_cgi.c --- mod_cgi.c 1997/07/17 22:27:34 1.49 +++ mod_cgi.c 1997/07/20 00:56:24 @@ -183,7 +183,7 @@ } static int log_script(request_rec *r, cgi_server_conf *conf, int ret, - char *dbuf, char *sbuf, FILE *script_in, FILE *script_err) + char *dbuf, char *sbuf, BUFF *script_in, BUFF *script_err) { table *hdrs_arr = r->headers_in; table_entry *hdrs = (table_entry *)hdrs_arr->elts; @@ -197,9 +197,9 @@ ((f = pfopen(r->pool, server_root_relative(r->pool, conf->logname), "a")) == NULL)) { /* Soak up script output */ - while (fgets(argsbuffer, HUGE_STRING_LEN-1, script_in)) + while (bgets(argsbuffer, HUGE_STRING_LEN, script_in)) continue; - while (fgets(argsbuffer, HUGE_STRING_LEN-1, script_err)) + while (bgets(argsbuffer, HUGE_STRING_LEN, script_err)) continue; return ret; } @@ -207,7 +207,7 @@ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */ fprintf(f, "%%%% [%s] %s %s%s%s %s\n", get_time(), r->method, r->uri, r->args ? "?" : "", r->args ? r->args : "", r->protocol); - /* "%% 500 /usr/local/etc/httpd/cgi-bin */ + /* "%% 500 /usr/local/etc/httpd/cgi-bin" */ fprintf(f, "%%%% %d %s\n", ret, r->filename); fputs("%request\n", f); @@ -216,7 +216,7 @@ fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val); } if ((r->method_number == M_POST || r->method_number == M_PUT) - && dbuf && *dbuf) { + && *dbuf) { fprintf(f, "\n%s\n", dbuf); } @@ -233,27 +233,27 @@ fprintf(f, "%s\n", sbuf); *argsbuffer = '\0'; - fgets(argsbuffer, HUGE_STRING_LEN-1, script_in); + bgets(argsbuffer, HUGE_STRING_LEN, script_in); if (*argsbuffer) { fputs("%stdout\n", f); fputs(argsbuffer, f); - while (fgets(argsbuffer, HUGE_STRING_LEN-1, script_in)) + while (bgets(argsbuffer, HUGE_STRING_LEN, script_in)) fputs(argsbuffer, f); fputs("\n", f); } *argsbuffer = '\0'; - fgets(argsbuffer, HUGE_STRING_LEN-1, script_err); + bgets(argsbuffer, HUGE_STRING_LEN, script_err); if (*argsbuffer) { fputs("%stderr\n", f); fputs(argsbuffer, f); - while (fgets(argsbuffer, HUGE_STRING_LEN-1, script_err)) + while (bgets(argsbuffer, HUGE_STRING_LEN, script_err)) fputs(argsbuffer, f); fputs("\n", f); } - pfclose(r->main ? r->main->pool : r->pool, script_in); - pfclose(r->main ? r->main->pool : r->pool, script_err); + bclose(script_in); + bclose(script_err); pfclose(r->pool, f); return ret; @@ -277,7 +277,6 @@ struct cgi_child_stuff *cld = (struct cgi_child_stuff *)child_stuff; request_rec *r = cld->r; char *argv0 = cld->argv0; - int nph = cld->nph; int child_pid; #ifdef DEBUG_CGI @@ -312,10 +311,6 @@ if (!cld->debug) error_log2stderr (r->server); -#if !defined(__EMX__) && !defined(WIN32) - if (nph) client_to_stdout (r->connection); -#endif - /* Transumute outselves into the script. * NB only ISINDEX scripts get decoded arguments. */ @@ -352,7 +347,7 @@ { int retval, nph, dbpos = 0; char *argv0, *dbuf = NULL; - FILE *script_out, *script_in, *script_err; + BUFF *script_out, *script_in, *script_err; char argsbuffer[HUGE_STRING_LEN]; int is_included = !strcmp (r->protocol, "INCLUDED"); void *sconf = r->server->module_config; @@ -421,15 +416,10 @@ * waiting for free_proc_chain to cleanup in the middle of an * SSI request -djg */ - spawn_child_err (r->main ? r->main->pool : r->pool, cgi_child, - (void *)&cld, - nph ? just_wait : kill_after_timeout, -#if defined(__EMX__) || defined(WIN32) - &script_out, &script_in, &script_err))) { -#else - &script_out, nph ? NULL : &script_in, - &script_err))) { -#endif + spawn_child_err_buff (r->main ? r->main->pool : r->pool, cgi_child, + (void *)&cld, + kill_after_timeout, + &script_out, &script_in, &script_err))) { log_reason ("couldn't spawn child process", r->filename, r); return SERVER_ERROR; } @@ -471,7 +461,7 @@ dbpos += dbsize; } reset_timeout(r); - if (fwrite(argsbuffer, sizeof(char), len_read, script_out) + if (bwrite(script_out, argsbuffer, len_read) < (size_t)len_read) { /* silly script stopped reading, soak up remaining message */ while (get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) @@ -480,20 +470,20 @@ } } - fflush (script_out); + bflush (script_out); signal (SIGPIPE, handler); kill_timeout (r); } - pfclose (r->main ? r->main->pool : r->pool, script_out); + bclose(script_out); /* Handle script return... */ if (script_in && !nph) { char *location, sbuf[MAX_STRING_LEN]; int ret; - if ((ret = scan_script_header_err(r, script_in, sbuf))) + if ((ret = scan_script_header_err_buff(r, script_in, sbuf))) return log_script(r, conf, ret, dbuf, sbuf, script_in, script_err); location = table_get (r->headers_out, "Location"); @@ -502,11 +492,9 @@ /* Soak up all the script output */ hard_timeout ("read from script", r); - while (fread(argsbuffer, sizeof(char), HUGE_STRING_LEN, script_in) - > 0) + while (bgets(argsbuffer, HUGE_STRING_LEN, script_in)) continue; - while (fread(argsbuffer, sizeof(char), HUGE_STRING_LEN, script_err) - > 0) + while (bgets(argsbuffer, HUGE_STRING_LEN, script_err)) continue; kill_timeout (r); @@ -535,24 +523,20 @@ send_http_header(r); if (!r->header_only) - send_fd(script_in, r); - pfclose (r->main ? r->main->pool : r->pool, script_in); + send_fb(script_in, r); - /* Soak up stderr */ soft_timeout("soaking script stderr", r); - while (!r->connection->aborted && - (fread(argsbuffer, sizeof(char), HUGE_STRING_LEN, script_err) > 0)) - continue; + while(bgets(argsbuffer, HUGE_STRING_LEN, script_err)) + continue; kill_timeout(r); - pfclose (r->main ? r->main->pool : r->pool, script_err); + + bclose(script_in); + bclose(script_out); } - if (nph) { -#if defined(__EMX__) || defined(WIN32) - while (fgets(argsbuffer, HUGE_STRING_LEN-1, script_in) != NULL) { - bputs(argsbuffer, r->connection->client); - } -#else + if (script_in && nph) { + send_fb(script_in, r); +#if !defined(__EMX__) && !defined(WIN32) waitpid(child_pid, (int*)0, 0); #endif } Index: util_script.c =================================================================== RCS file: /export/home/cvs/apache/src/util_script.c,v retrieving revision 1.66 diff -u -r1.66 util_script.c --- util_script.c 1997/07/15 21:39:59 1.66 +++ util_script.c 1997/07/20 00:56:26 @@ -312,7 +312,9 @@ } } -API_EXPORT(int) scan_script_header_err(request_rec *r, FILE *f, char *buffer) + +static int scan_script_header_err_core (request_rec *r, char *buffer, + int (*getsfunc)(char *, int, void *), void *getsfunc_data) { char x[MAX_STRING_LEN]; char *w, *l; @@ -325,7 +327,7 @@ while(1) { - if (fgets(w, MAX_STRING_LEN-1, f) == NULL) { + if ((*getsfunc)(w, MAX_STRING_LEN-1, getsfunc_data) == 0) { kill_timeout (r); log_reason ("Premature end of script headers", r->filename, r); return SERVER_ERROR; @@ -354,7 +356,7 @@ if (!buffer) /* Soak up all the script output --- may save an outright kill */ - while (fgets(w, MAX_STRING_LEN-1, f) != NULL) + while ((*getsfunc)(w, MAX_STRING_LEN-1, getsfunc_data)) continue; kill_timeout (r); @@ -401,6 +403,28 @@ } } } + +static int getsfunc_FILE (char *buf, int len, void *f) +{ + return fgets (buf, len, (FILE *)f) != NULL; +} + +API_EXPORT(int) scan_script_header_err(request_rec *r, FILE *f, char *buffer) +{ + return scan_script_header_err_core (r, buffer, getsfunc_FILE, f); +} + +static int getsfunc_BUFF (char *w, int len, void *fb) +{ + return bgets (w, len, (BUFF *)fb) > 0; +} + +API_EXPORT(int) scan_script_header_err_buff(request_rec *r, BUFF *fb, + char *buffer) +{ + return scan_script_header_err_core (r, buffer, getsfunc_BUFF, fb); +} + API_EXPORT(void) send_size(size_t size, request_rec *r) { char ss[20]; Index: util_script.h =================================================================== RCS file: /export/home/cvs/apache/src/util_script.h,v retrieving revision 1.21 diff -u -r1.21 util_script.h --- util_script.h 1997/07/15 21:40:00 1.21 +++ util_script.h 1997/07/20 00:56:27 @@ -64,6 +64,7 @@ API_EXPORT(void) add_common_vars(request_rec *r); #define scan_script_header(a1,a2) scan_script_header_err(a1,a2,NULL) API_EXPORT(int) scan_script_header_err(request_rec *r, FILE *f, char *buffer); +API_EXPORT(int) scan_script_header_err_buff(request_rec *r, BUFF *f, char *buffer); API_EXPORT(void) send_size(size_t size, request_rec *r); API_EXPORT(int) call_exec (request_rec *r, char *argv0, char **env, int shellcmd);