Blaise Tarr wrote:

>With 2.0.40 content coming from mod_proxy and mod_cgi is no longer
>streamed to the client but sent in one big chunk.  It appears that the
>C-L filter now buffers the data in every case.  For certain
>applications this is a very undesirable feature, and it would be very
>helpful if the C-L filter could be bypassed (and simply not send back
>a C-L header).  In 2.0.32 streaming behaved beautifully.  Then in
>2.0.36 and 2.0.39 the C-L filter started buffering large chunks.  Now
>in 2.0.40 everything is buffered and a C-L header is always inserted.
>Could the 2.0.32 streaming/C-L behavior be brought back, perhaps
>enabled by a config option?
>
>Blaise
>  
>

Here's a patch that removes the buffering.  Let me know if
it solves the proxy streaming problem.

This patch takes a simplified approach to the C-L computation.
The first time the C-L filter is called for a request, it checks
whether it's been passed an entire response (as denoted by an
EOS at end of the brigade).  If so, it sets the content-length
for the response header.  If not, it just passes all the data
on to the next filter.

With this patch, we'll end up not sending a C-L header on most
shtml or CGI pages.  As a result, the server will add a
"Connection: close" for HTTP/1.0 clients and will use chunked
encoding for HTTP/1.1 clients.

Brian

Index: server/protocol.c
===================================================================
RCS file: /home/cvs/httpd-2.0/server/protocol.c,v
retrieving revision 1.115
diff -u -r1.115 protocol.c
--- server/protocol.c   13 Aug 2002 14:27:39 -0000      1.115
+++ server/protocol.c   18 Aug 2002 21:52:29 -0000
@@ -1200,9 +1200,10 @@
 }
 
 struct content_length_ctx {
-    apr_bucket_brigade *saved;
-    int compute_len;
-    apr_size_t curr_len;
+    int data_sent;  /* true if the C-L filter has already sent at
+                     * least one bucket on to the next output filter
+                     * for this request
+                     */
 };
 
 /* This filter computes the content length, but it also computes the number
@@ -1214,140 +1215,77 @@
 {
     request_rec *r = f->r;
     struct content_length_ctx *ctx;
-    apr_status_t rv;
     apr_bucket *e;
-    int eos = 0, flush = 0, partial_send_okay = 0;
-    apr_bucket_brigade *more, *split;
-    apr_read_type_e eblock = APR_NONBLOCK_READ;
+    int eos = 0;
 
-    ctx = f->ctx;
-    if (!ctx) { /* first time through */
-        f->ctx = ctx = apr_pcalloc(r->pool, sizeof(struct content_length_ctx));
-        ctx->compute_len = 1;   /* Assume we will compute the length */
-        ctx->saved = apr_brigade_create(r->pool, f->c->bucket_alloc);
-    }
+    int can_send_content_length;
 
-    /* Humm, is this check the best it can be?
-     * - protocol >= HTTP/1.1 implies support for chunking
-     * - non-keepalive implies the end of byte stream will be signaled
-     *    by a connection close
-     * In both cases, we can send bytes to the client w/o needing to
-     * compute content-length.
-     * Todo:
-     * We should be able to force connection close from this filter
-     * when we see we are buffering too much.
+    /* We can only set a C-L in the response header if we
+     * haven't already sent any buckets on to the next
+     * output filter for this request.
      */
-    if ((r->proto_num >= HTTP_VERSION(1, 1)) ||
-        (r->connection->keepalive == AP_CONN_CLOSE)) {
-        partial_send_okay = 1;
+    ctx = f->ctx;
+    if (!ctx) {
+        f->ctx = ctx = apr_palloc(r->pool, sizeof(struct content_length_ctx));
+        ctx->data_sent = 0;
+        can_send_content_length = 1;
+    }
+    else {
+        can_send_content_length = (ctx->data_sent == 0);
     }
 
-    more = b;
-    while (more) {
-        b = more;
-        more = NULL;
-        split = NULL;
-        flush = 0;
-
-        e = APR_BRIGADE_FIRST(b);
-        while (e != APR_BRIGADE_SENTINEL(b)) {
-            const char *ignored;
+    /* Loop through this set of buckets to compute their length
+     */
+    e = APR_BRIGADE_FIRST(b);
+    while (e != APR_BRIGADE_SENTINEL(b)) {
+        if (APR_BUCKET_IS_EOS(e)) {
+            eos = 1;
+            break;
+        }
+        if (e->length == -1) {
             apr_size_t len;
-            len = 0;
-            if (APR_BUCKET_IS_EOS(e)) {
-                eos = 1;
-                break;
-            }
-            else if (APR_BUCKET_IS_FLUSH(e)) {
-                if (partial_send_okay) {
-                    split = b;
-                    more = apr_brigade_split(b, APR_BUCKET_NEXT(e));
-                    break;
-                }
-            }
-            else if ((ctx->curr_len > 4 * AP_MIN_BYTES_TO_WRITE)) {
-                /* If we've accumulated more than 4xAP_MIN_BYTES_TO_WRITE and
-                 * the client supports chunked encoding, send what we have
-                 * and come back for more.
-                 */
-                if (partial_send_okay) {
-                    split = b;
-                    more = apr_brigade_split(b, e);
-                    break;
-                }
-            }
-            if (e->length == -1) { /* if length unknown */
-                rv = apr_bucket_read(e, &ignored, &len, eblock);
-                if (rv == APR_SUCCESS) {
-                    /* Attempt a nonblocking read next time through */
-                    eblock = APR_NONBLOCK_READ;
-                }
-                else if (APR_STATUS_IS_EAGAIN(rv)) {
-                    /* Make the next read blocking.  If the client supports
-                     * chunked encoding, flush the filter stack to the network.
-                     */
-                    eblock = APR_BLOCK_READ;
-                    if (partial_send_okay) {
-                        split = b;
-                        more = apr_brigade_split(b, e);
-                        flush = 1;
-                        break;
-                    }
-                    continue;
-                }
-                else if (rv != APR_EOF) {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
-                                  "ap_content_length_filter: "
-                                  "apr_bucket_read() failed");
+            const char *ignored;
+            apr_status_t rv;
+
+            /* This is probably a pipe bucket.  Send everything
+             * prior to this, and then read the data for this bucket.
+             */
+            if (e != APR_BRIGADE_FIRST(b)) {
+                apr_bucket_brigade *split = apr_brigade_split(b, e);
+                rv = ap_pass_brigade(f->next, b);
+                if (rv != APR_SUCCESS) {
+                    apr_brigade_destroy(split);
                     return rv;
                 }
+                b = split;
+                can_send_content_length = 0;
+                ctx->data_sent = 1;
+            }
+            rv = apr_bucket_read(e, &ignored, &len, APR_BLOCK_READ);
+            if (rv == APR_SUCCESS) {
+                r->bytes_sent += len;
+            }
+            else if (rv != APR_EOF) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+                              "ap_content_length_filter: "
+                              "apr_bucket_read() failed");
+                return rv;
             }
-            else {
-                len = e->length;
-            }
-
-            ctx->curr_len += len;
-            r->bytes_sent += len;
-            e = APR_BUCKET_NEXT(e);
         }
-
-        if (split) {
-            ctx->compute_len = 0;  /* Ooops, can't compute the length now */
-            ctx->curr_len = 0;
-
-            APR_BRIGADE_PREPEND(split, ctx->saved);
-
-            if (flush) {
-                rv = ap_fflush(f->next, split);
-            }
-            else {
-                rv = ap_pass_brigade(f->next, split);
-            }
-
-            if (rv != APR_SUCCESS)
-                return rv;
+        else {
+            r->bytes_sent += e->length;
         }
+        e = APR_BUCKET_NEXT(e);
     }
 
-    if ((ctx->curr_len < AP_MIN_BYTES_TO_WRITE) && !eos) {
-        return ap_save_brigade(f, &ctx->saved, &b,
-                               (r->main) ? r->main->pool : r->pool);
-    }
-
-    if (ctx->compute_len) {
-        /* save the brigade; we can't pass any data to the next
-         * filter until we have the entire content length
-         */
-        if (!eos) {
-            return ap_save_brigade(f, &ctx->saved, &b, r->pool);
-        }
-
+    /* If we've now seen the entire response and it's otherwise
+     * okay to set the C-L in the response header, do so now:
+     */
+    if (can_send_content_length && eos) {
         ap_set_content_length(r, r->bytes_sent);
     }
 
-    APR_BRIGADE_PREPEND(b, ctx->saved);
-
-    ctx->curr_len = 0;
+    ctx->data_sent = 1;
     return ap_pass_brigade(f->next, b);
 }
 

Reply via email to