dgaudet 99/06/24 00:29:33
Modified: mpm/src/include buff.h mpm/src/main buff.c http_protocol.c iol_unix.c mpm/src/modules/mpm/prefork prefork.c Log: new-fangled BUFF... this could easily be broken, but hey, the one that was in here before was broken in different ways... I've served up a few pages with this one. Revision Changes Path 1.4 +38 -6 apache-2.0/mpm/src/include/buff.h Index: buff.h =================================================================== RCS file: /home/cvs/apache-2.0/mpm/src/include/buff.h,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- buff.h 1999/06/18 23:34:58 1.3 +++ buff.h 1999/06/24 07:29:29 1.4 @@ -65,6 +65,37 @@ #include <stdarg.h> #include "ap_iol.h" +/* + A BUFF is an i/o object which can be used in any of the following + output modes: + + blocking, buffered + blocking, buffered, HTTP-chunked + blocking, unbuffered + blocking, unbuffered, HTTP-chunked + non-blocking, unbuffered + non-blocking, unbuffered, HTTP-chunked + + In all the blocking modes, a bwrite(fb, buf, len) will return less + than len only in an error state. The error may be deferred until + the next use of fb. + + In the non-blocking, chunked mode, the caller of bwrite() makes a + guarantee that if a partial write occurs, they will call back later + with at least as many bytes to write -- prior to disabling chunking. + This is a protocol correctness requirement -- the chunk length may + already have hit the wire, and is in essence "committed". + + bputc, bputs, bvputs, and bprintf are supported only in the buffered + modes. + + The following input modes are supported: + + blocking, buffered + blocking, unbuffered + non-blocking, unbuffered +*/ + /* Reading is buffered */ #define B_RD (1) /* Writing is buffered */ @@ -82,16 +113,12 @@ #undef B_ERROR #endif #define B_ERROR (48) -/* TODO: implement chunked encoding as a layer */ +/* Use chunked writing */ +#define B_CHUNK (64) /* bflush() if a read would block */ /* TODO: #define B_SAFEREAD (128) */ -/* buffer is a socket */ -#define B_SOCKET (256) - /* caller expects non-blocking behaviour */ #define B_NONBLOCK (512) -/* non-blocking bit set on fd */ -#define B_NONBLOCK_SET (1024) /* TODO: implement a ebcdic/ascii conversion layers */ @@ -103,10 +130,15 @@ unsigned char *inptr; /* pointer to next location to read */ int incnt; /* number of bytes left to read from input buffer; * always 0 if had a read error */ + int outchunk; /* location of chunk header when chunking */ int outcnt; /* number of byte put in output buffer */ unsigned char *inbase; unsigned char *outbase; int bufsiz; + int chunk_overcommit; /* when we start a chunk and get a partial write we + * keep track of the #remaining bytes in the chunk + * here + */ void (*error) (BUFF *fb, int op, void *data); void *error_data; long int bytes_sent; /* number of bytes actually written */ 1.5 +266 -143 apache-2.0/mpm/src/main/buff.c Index: buff.c =================================================================== RCS file: /home/cvs/apache-2.0/mpm/src/main/buff.c,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- buff.c 1999/06/20 12:25:54 1.4 +++ buff.c 1999/06/24 07:29:31 1.5 @@ -76,6 +76,17 @@ #define DEFAULT_BUFSIZE (4096) #endif +/* the maximum size of any chunk */ +#ifndef MAX_CHUNK_SIZE +#define MAX_CHUNK_SIZE (0x8000) +#endif + +/* This must be enough to represent MAX_CHUNK_SIZE in hex, + * plus two extra characters. + */ +#ifndef CHUNK_HEADER_SIZE +#define CHUNK_HEADER_SIZE (6) +#endif /* bwrite()s of greater than this size can result in a large_write() call, * which can result in a writev(). It's a little more work to set up the @@ -88,7 +99,6 @@ #define LARGE_WRITE_THRESHOLD 31 #endif - /* * Buffered I/O routines. * These are a replacement for the stdio routines. @@ -136,15 +146,18 @@ fb = ap_palloc(p, sizeof(BUFF)); fb->pool = p; fb->bufsiz = DEFAULT_BUFSIZE; - fb->flags = flags & (B_RDWR | B_SOCKET); + fb->flags = flags & B_RDWR; if (flags & B_RD) fb->inbase = ap_palloc(p, fb->bufsiz); else fb->inbase = NULL; + /* overallocate so that we can put a chunk trailer of CRLF into this + * buffer... and possibly the beginning of a new chunk + */ if (flags & B_WR) - fb->outbase = ap_palloc(p, fb->bufsiz); + fb->outbase = ap_palloc(p, fb->bufsiz + 2 + CHUNK_HEADER_SIZE + 1); else fb->outbase = NULL; @@ -152,8 +165,10 @@ fb->incnt = 0; fb->outcnt = 0; + fb->outchunk = -1; fb->error = NULL; fb->bytes_sent = 0; + fb->chunk_overcommit = 0; return fb; } @@ -183,6 +198,11 @@ return 0; case BO_TIMEOUT: + fb->flags &= ~B_NONBLOCK; + if (optval == 0) { + fb->flags |= B_NONBLOCK; + /* XXX: should remove B_WR now... */ + } return iol_setopt(&fb->iol, AP_IOL_TIMEOUT, optval); } errno = EINVAL; @@ -207,19 +227,82 @@ errno = EINVAL; return -1; } + +static void start_chunk(BUFF *fb) +{ + fb->outchunk = fb->outcnt; + fb->outcnt += CHUNK_HEADER_SIZE; +} + +static int end_chunk(BUFF *fb, int extra) +{ + int i; + unsigned char *strp; + int chunk_size; + + chunk_size = fb->outcnt - fb->outchunk - CHUNK_HEADER_SIZE + extra; + if (chunk_size == 0) { + /* nothing was written into this chunk, and we can't write a 0 size + * chunk because that signifies EOF, so just erase it + */ + fb->outcnt = fb->outchunk; + fb->outchunk = -1; + return 0; + } + + if (chunk_size > MAX_CHUNK_SIZE) { + extra -= chunk_size - MAX_CHUNK_SIZE; + chunk_size = MAX_CHUNK_SIZE; + } + + /* we know this will fit because of how we wrote it in start_chunk() */ + i = ap_snprintf((char *) &fb->outbase[fb->outchunk], CHUNK_HEADER_SIZE, + "%x", chunk_size); + + /* we may have to tack some trailing spaces onto the number we just wrote + * in case it was smaller than our estimated size. We've also written + * a \0 into the buffer with ap_snprintf so we might have to put a + * \r back in. + */ + strp = &fb->outbase[fb->outchunk + i]; + while (i < CHUNK_HEADER_SIZE - 2) { + *strp++ = ' '; + ++i; + } + *strp++ = '\015'; + *strp = '\012'; + + /* tack on the trailing CRLF, we've reserved room for this */ + fb->outbase[fb->outcnt++] = '\015'; + fb->outbase[fb->outcnt++] = '\012'; + + fb->outchunk = -1; + + return extra; +} -static int bflush_core(BUFF *fb); + /* * Set a flag on (1) or off (0). */ API_EXPORT(int) ap_bsetflag(BUFF *fb, int flag, int value) { + int old_flags = fb->flags; + if (value) { fb->flags |= flag; + /* start chunking if we haven't already */ + if ((flag ^ old_flags) & B_CHUNK) { + start_chunk(fb); + } } else { fb->flags &= ~flag; + /* stop chunking if we haven't already */ + if ((flag ^ old_flags) & B_CHUNK) { + end_chunk(fb, 0); + } } return value; } @@ -236,7 +319,7 @@ } else if (rv == -1) { fb->saved_errno = errno; - if (errno != EWOULDBLOCK) { + if (errno != EAGAIN) { doerror(fb, B_RD); } } @@ -443,14 +526,14 @@ return buf[0]; } -/* A wrapper for write which deals with error conditions and +/* A wrapper for writev which deals with error conditions and * bytes_sent. */ -static int write_with_errors(BUFF *fb, const void *buf, int nbyte) +static int writev_with_errors(BUFF *fb, const struct iovec *vec, int nvec) { int rv; - rv = iol_write(&fb->iol, buf, nbyte); + rv = iol_writev(&fb->iol, vec, nvec); if (rv == -1) { fb->saved_errno = errno; if (errno != EAGAIN) { @@ -458,22 +541,144 @@ } return -1; } - else if (rv == 0) { - errno = EAGAIN; - return -1; - } fb->bytes_sent += rv; return rv; } -/* A wrapper for writev which deals with error conditions and + +static int writev_it_all(BUFF *fb, struct iovec *vec, int nvec) +{ + int i; + int rv; + int total; + + i = 0; + total = 0; + while (i < nvec) { + rv = writev_with_errors(fb, vec, 2); + if (rv < 0) { + return total ? -1 : total; + } + total += rv; + if (fb->flags & B_NONBLOCK) { + return total; + } + /* recalculate to deal with partial writes */ + while (rv > 0) { + if (rv < vec[i].iov_len) { + vec[i].iov_base = (char *) vec[i].iov_base + rv; + vec[i].iov_len -= rv; + break; + } + else { + rv -= vec[i].iov_len; + ++i; + } + } + } + return total; +} + + +/* write the contents of fb->outbase, and buf, + stop at first partial write for a non-blocking buff + + return number of bytes of buf which were written + -1 for errors +*/ +static int large_write(BUFF *fb, const char *buf, int nbyte) +{ + struct iovec vec[2]; + int nvec; + int rv; + + ap_assert(nbyte > 0); + if (fb->outcnt) { + vec[0].iov_base = fb->outbase; + vec[0].iov_len = fb->outcnt; + vec[1].iov_base = (void *)buf; + vec[1].iov_len = nbyte; + nvec = 2; + } + else { + vec[0].iov_base = (void *)buf; + vec[0].iov_len = nbyte; + nvec = 1; + } + rv = writev_it_all(fb, vec, nvec); + if (rv <= 0) { + return rv; + } + if (rv < fb->outcnt) { + /* shift bytes forward in buffer */ + memmove(fb->outbase, fb->outbase + rv, fb->outcnt - rv); + fb->outcnt -= rv; + return 0; + } + rv -= fb->outcnt; + fb->outcnt = 0; + return rv; +} + + +static int large_write_chunk(BUFF *fb, const char *buf, int nbyte) +{ + int rv; + int amt; + int total; + + ap_assert(nbyte > 0); + if (fb->chunk_overcommit) { + amt = nbyte > fb->chunk_overcommit ? fb->chunk_overcommit : nbyte; + rv = large_write(fb, buf, amt); + if (rv <= 0) { + return rv; + } + fb->chunk_overcommit -= rv; + if (fb->chunk_overcommit == 0) { + fb->outbase[0] = '\015'; + fb->outbase[1] = '\012'; + fb->outcnt = 2; + start_chunk(fb); + } + if (rv < amt || amt == nbyte) { + return rv; + } + nbyte -= rv; + buf += rv; + } + ap_assert(fb->chunk_overcommit == 0 && fb->outchunk != -1); + total = 0; + do { + amt = end_chunk(fb, nbyte); + rv = large_write(fb, buf, amt); + if (rv < amt) { + if (rv < 0) { + fb->chunk_overcommit = amt; + return total ? total : -1; + } + fb->chunk_overcommit = amt - rv; + return total + rv; + } + fb->outbase[0] = '\015'; + fb->outbase[1] = '\012'; + fb->outcnt = 2; + start_chunk(fb); + nbyte -= amt; + buf += amt; + total += amt; + } while (nbyte); + return total; +} + +/* A wrapper for write which deals with error conditions and * bytes_sent. */ -static int writev_with_errors(BUFF *fb, const struct iovec *vec, int nvec) +static int write_with_errors(BUFF *fb, const void *buf, int nbyte) { int rv; - rv = iol_writev(&fb->iol, vec, nvec); + rv = iol_write(&fb->iol, buf, nbyte); if (rv == -1) { fb->saved_errno = errno; if (errno != EAGAIN) { @@ -481,42 +686,36 @@ } return -1; } - else if (rv == 0) { - errno = EAGAIN; - return -1; - } fb->bytes_sent += rv; return rv; } -/* - * Used to combine the contents of the fb buffer, and a large buffer - * passed in. The return code is how many bytes of buf were written, - * or -1. - */ -static int large_write(BUFF *fb, const void *buf, int nbyte) +static int bflush_core(BUFF *fb) { - struct iovec vec[2]; + int total; int rv; - vec[0].iov_base = (void *) fb->outbase; - vec[0].iov_len = fb->outcnt; - vec[1].iov_base = (void *) buf; - vec[1].iov_len = nbyte; - rv = writev_with_errors(fb, vec, 2); - if (rv >= fb->outcnt) { - rv -= fb->outcnt; - fb->outcnt = 0; - return rv; + if (fb->flags & B_CHUNK) { + end_chunk(fb, 0); } - else if (rv > 0) { - /* shift bytes forward in buffer */ - memmove(fb->outbase, fb->outbase + rv, fb->outcnt - rv); + total = 0; + while (fb->outcnt > 0) { + rv = write_with_errors(fb, fb->outbase + total, fb->outcnt); + if (rv <= 0) { + if (total) { + memmove(fb->outbase, fb->outbase + total, fb->outcnt); + return total; + } + return -1; + } fb->outcnt -= rv; - return 0; + total += rv; } - return rv; + if (fb->flags & B_CHUNK) { + start_chunk(fb); + } + return total; } @@ -529,7 +728,8 @@ */ API_EXPORT(int) ap_bwrite(BUFF *fb, const void *buf, int nbyte) { - int i, nwr; + int amt; + int total; if (fb->flags & (B_WRERR | B_EOUT)) { errno = fb->saved_errno; @@ -538,10 +738,6 @@ if (nbyte == 0) return 0; - if (!(fb->flags & B_WR)) { - return write_with_errors(fb, buf, nbyte); - } - /* * Detect case where we're asked to write a large buffer, and combine our * current buffer with it in a single writev(). Note we don't consider @@ -549,104 +745,31 @@ * us to use writev() too frequently. In those cases we really should just * start a new buffer. */ - if (fb->outcnt > 0 && nbyte > LARGE_WRITE_THRESHOLD - && nbyte + fb->outcnt >= fb->bufsiz) { - int n=large_write(fb, buf, nbyte); - if (n == nbyte) - return nbyte; - buf+=n; - nbyte-=n; - } - -/* - * Whilst there is data in the buffer, keep on adding to it and writing it - * out - */ - nwr = 0; - while (fb->outcnt > 0) { -/* can we accept some data? */ - i = fb->bufsiz - fb->outcnt; - if (i > 0) { - if (i > nbyte) - i = nbyte; - memcpy(fb->outbase + fb->outcnt, buf, i); - fb->outcnt += i; - nbyte -= i; - buf = i + (const char *) buf; - nwr += i; - if (nbyte == 0) - return nwr; /* return if none left */ - } - -/* the buffer must be full */ - i = write_with_errors(fb, fb->outbase, fb->outcnt); - if (i <= 0) { - return nwr ? nwr : -1; - } - - /* deal with a partial write */ - if (i < fb->outcnt) { - memmove(fb->outbase, fb->outbase + i, fb->outcnt - i); - fb->outcnt -= i; - } - else - fb->outcnt = 0; - - if (fb->flags & B_EOUT) - return -1; - } -/* we have emptied the file buffer. Now try to write the data from the - * original buffer until there is less than bufsiz left. - */ - while (nbyte >= fb->bufsiz) { - i = write_with_errors(fb, buf, nbyte); - if (i <= 0) { - return nwr ? nwr : -1; - } - - buf = i + (const char *) buf; - nwr += i; - nbyte -= i; - - if (fb->flags & B_EOUT) - return -1; - } -/* copy what's left to the file buffer */ - /* assert(fb->outcnt == 0); */ - if (nbyte > 0) - memcpy(fb->outbase, buf, nbyte); - fb->outcnt = nbyte; - nwr += nbyte; - return nwr; -} - - -static int bflush_core(BUFF *fb) -{ - int i; - - while (fb->outcnt > 0) { - i = write_with_errors(fb, fb->outbase, fb->outcnt); - if (i <= 0) - return -1; - - /* - * We should have written all the data, but if the fd was in a - * strange (non-blocking) mode, then we might not have done so. - */ - if (i < fb->outcnt) { - memmove(fb->outbase, fb->outbase + i, fb->outcnt - i); - } - fb->outcnt -= i; - - /* If a soft timeout occurs while flushing, the handler should - * have set the buffer flag B_EOUT. - */ - if (fb->flags & B_EOUT) - return -1; - } - - return 0; + if (!(fb->flags & B_WR) + || (nbyte > LARGE_WRITE_THRESHOLD && nbyte + fb->outcnt >= fb->bufsiz)) { + if (fb->flags & B_CHUNK) { + return large_write_chunk(fb, buf, nbyte); + } + return large_write(fb, buf, nbyte); + } + + /* at this point we know that nbyte < fb->bufsize */ + amt = fb->bufsiz - fb->outcnt; + total = 0; + if (nbyte > amt) { + memcpy(fb->outbase + fb->outcnt, buf, amt); + fb->outcnt += amt; + buf = (const char *) buf + amt; + nbyte -= amt; + if (bflush_core(fb) < amt) { + return amt; + } + total = amt; + } + /* now we know that nbyte < fb->bufsiz */ + memcpy(fb->outbase + fb->outcnt, buf, nbyte); + fb->outcnt += nbyte; + return total + nbyte; } /* 1.6 +0 -4 apache-2.0/mpm/src/main/http_protocol.c Index: http_protocol.c =================================================================== RCS file: /home/cvs/apache-2.0/mpm/src/main/http_protocol.c,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- http_protocol.c 1999/06/20 11:19:46 1.5 +++ http_protocol.c 1999/06/24 07:29:31 1.6 @@ -1551,10 +1551,8 @@ r->sent_bodyct = 1; /* Whatever follows is real body stuff... */ /* Set buffer flags for the body */ -#if 0 /* TODO: implemented chunked layer */ if (r->chunked) ap_bsetflag(r->connection->client, B_CHUNK, 1); -#endif #ifdef CHARSET_EBCDIC if (!convert) ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, convert); @@ -1573,9 +1571,7 @@ * Turn off chunked encoding --- we can only do this once. */ r->chunked = 0; -#if 0 /* TODO: implemented chunked layer */ ap_bsetflag(r->connection->client, B_CHUNK, 0); -#endif ap_rputs("0\015\012", r); /* If we had footer "headers", we'd send them now */ 1.5 +1 -1 apache-2.0/mpm/src/main/iol_unix.c Index: iol_unix.c =================================================================== RCS file: /home/cvs/apache-2.0/mpm/src/main/iol_unix.c,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- iol_unix.c 1999/06/19 20:40:17 1.4 +++ iol_unix.c 1999/06/24 07:29:31 1.5 @@ -164,7 +164,7 @@ if (set_nonblock(fd->fd)) { \ return -1; \ } \ - fd->flags |= B_NONBLOCK_SET; \ + fd->flags |= FD_NONBLOCKING_SET; \ } \ \ /* try writing, ignoring EINTR, the upper layer has to handle \ 1.2 +1 -1 apache-2.0/mpm/src/modules/mpm/prefork/prefork.c Index: prefork.c =================================================================== RCS file: /home/cvs/apache-2.0/mpm/src/modules/mpm/prefork/prefork.c,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- prefork.c 1999/06/24 01:57:58 1.1 +++ prefork.c 1999/06/24 07:29:32 1.2 @@ -2630,7 +2630,7 @@ (void) ap_update_child_status(my_child_num, SERVER_BUSY_READ, (request_rec *) NULL); - conn_io = ap_bcreate(ptrans, B_RDWR | B_SOCKET); + conn_io = ap_bcreate(ptrans, B_RDWR); #ifdef B_SFIO (void) sfdisc(conn_io->sf_in, SF_POPDISC);