You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ia...@apache.org on 2004/04/16 07:12:22 UTC

cvs commit: httpd-2.0/modules/filters mod_deflate.c

ianh        2004/04/15 22:12:22

  Modified:    docs/manual/mod mod_deflate.xml
               .        CHANGES
               modules/filters mod_deflate.c
  Log:
  mod_deflate:
  - New option for DEFLATE output file (force-gzip), which skips checking the accept-encoding header.
  - New output filter 'INFLATE' for uncompressing responses.
  
  Submitted by:   Nick Kew <Nick at WebThing dot com>
  Reviewed by:    Ian Holsman
  
  Revision  Changes    Path
  1.23      +28 -0     httpd-2.0/docs/manual/mod/mod_deflate.xml
  
  Index: mod_deflate.xml
  ===================================================================
  RCS file: /home/cvs/httpd-2.0/docs/manual/mod/mod_deflate.xml,v
  retrieving revision 1.22
  retrieving revision 1.23
  diff -u -r1.22 -r1.23
  --- mod_deflate.xml	22 Mar 2004 21:06:31 -0000	1.22
  +++ mod_deflate.xml	16 Apr 2004 05:12:21 -0000	1.23
  @@ -147,8 +147,36 @@
           The <code>DEFLATE</code> filter is always inserted after RESOURCE
           filters like PHP or SSI. It never touches internal subrequests.
         </note>
  +      <note><title>Note</title>
  +        There is a environment variable <code>force-gzip</code>,
  +        set via <directive module="core">SetEnv</directive>, which
  +        will ignore the accept-encoding setting of your browser and will
  +        send compressed output.
  +      </note>
  +
       </section>
  +    <section id="inflate"><title>Output Decompression</title>
  +      <p>The <module>mod_deflate</module> module also provides a filter for
  +      inflating/uncompressing a gzip compressed response body. In order to activate
  +      this feature you have to insert the <code>INFLATE</code> filter into
  +      the outputfilter chain using <directive module="core"
  +      >SetOutputFilter</directive> or <directive module="mod_mime"
  +      >AddOutputFilter</directive>, for example:</p>
  +
  +      <example>
  +        &lt;Location /dav-area&gt;<br />
  +        <indent>
  +          ProxyPass http://example.com/<br />
  +          SetOutputFilter INFLATE<br />
  +        </indent>
  +        &lt;/Location&gt;
  +      </example>
   
  +      <p>This Example will uncompress gzip'ed output from example.com, so other
  +      filters can do further processing with it.
  +      </p>
  +      
  +    </section>
       <section id="input"><title>Input Decompression</title>
         <p>The <module>mod_deflate</module> module also provides a filter for
         decompressing a gzip compressed request body . In order to activate
  
  
  
  1.1458    +4 -0      httpd-2.0/CHANGES
  
  Index: CHANGES
  ===================================================================
  RCS file: /home/cvs/httpd-2.0/CHANGES,v
  retrieving revision 1.1457
  retrieving revision 1.1458
  diff -u -r1.1457 -r1.1458
  --- CHANGES	15 Apr 2004 20:17:06 -0000	1.1457
  +++ CHANGES	16 Apr 2004 05:12:22 -0000	1.1458
  @@ -2,6 +2,10 @@
   
     [Remove entries to the current 2.0 section below, when backported]
   
  +  *) mod_deflate: New option for DEFLATE output file (force-gzip),
  +     new output filter 'INFLATE' for uncompressing responses.
  +     [Nick Kew <Nick at WebThing dot com>, Ian Holsman]
  +
     *) Added new module mod_version, which provides version dependent
        configuration containers.  [Andr� Malo]
   
  
  
  
  1.45      +249 -26   httpd-2.0/modules/filters/mod_deflate.c
  
  Index: mod_deflate.c
  ===================================================================
  RCS file: /home/cvs/httpd-2.0/modules/filters/mod_deflate.c,v
  retrieving revision 1.44
  retrieving revision 1.45
  diff -u -r1.44 -r1.45
  --- mod_deflate.c	9 Feb 2004 20:29:19 -0000	1.44
  +++ mod_deflate.c	16 Apr 2004 05:12:22 -0000	1.45
  @@ -16,8 +16,7 @@
   /*
    * mod_deflate.c: Perform deflate transfer-encoding on the fly
    *
  - * Written by Ian Holsman
  - *
  + * Written by Ian Holsman, Justin Erenkrantz, and Nick Kew
    */
   
   /*
  @@ -257,7 +256,7 @@
        */
       if (!ctx) {
           char *buf, *token;
  -        const char *encoding, *accepts;
  +        const char *encoding;
   
           /* only work on main request/no subrequests */
           if (r->main) {
  @@ -320,7 +319,7 @@
                       strcmp(token, "8bit") && strcmp(token, "binary")) {
   
                       ap_remove_output_filter(f);
  -                    return ap_pass_brigade(f->next, bb);			
  +                    return ap_pass_brigade(f->next, bb);
                   }
   
                   /* Otherwise, skip token */
  @@ -337,34 +336,40 @@
            */
           apr_table_setn(r->headers_out, "Vary", "Accept-Encoding");
   
  -        /* if they don't have the line, then they can't play */
  -        accepts = apr_table_get(r->headers_in, "Accept-Encoding");
  -        if (accepts == NULL) {
  -            ap_remove_output_filter(f);
  -            return ap_pass_brigade(f->next, bb);
  -        }
   
  -        token = ap_get_token(r->pool, &accepts, 0);
  -        while (token && token[0] && strcasecmp(token, "gzip")) {
  -            /* skip parameters, XXX: ;q=foo evaluation? */
  -            while (*accepts == ';') { 
  -                ++accepts;
  -                token = ap_get_token(r->pool, &accepts, 1);
  +        /* force-gzip will just force it out regardless if the browser
  +         * can actually do anything with it.
  +         */
  +        if (apr_table_get(r->subprocess_env, "force-gzip") != NULL) {
  +            const char *accepts;
  +            /* if they don't have the line, then they can't play */
  +            accepts = apr_table_get(r->headers_in, "Accept-Encoding");
  +            if (accepts == NULL) {
  +                ap_remove_output_filter(f);
  +                return ap_pass_brigade(f->next, bb);
               }
   
  -            /* retrieve next token */
  -            if (*accepts == ',') {
  -                ++accepts;
  +            token = ap_get_token(r->pool, &accepts, 0);
  +            while (token && token[0] && strcasecmp(token, "gzip")) {
  +                /* skip parameters, XXX: ;q=foo evaluation? */
  +                while (*accepts == ';') { 
  +                    ++accepts;
  +                    token = ap_get_token(r->pool, &accepts, 1);
  +                }
  +
  +                /* retrieve next token */
  +                if (*accepts == ',') {
  +                    ++accepts;
  +                }
  +                token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
               }
  -            token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
  -        }
   
  -        /* No acceptable token found. */
  -        if (token == NULL || token[0] == '\0') {
  -            ap_remove_output_filter(f);
  -            return ap_pass_brigade(f->next, bb);
  +            /* No acceptable token found. */
  +            if (token == NULL || token[0] == '\0') {
  +                ap_remove_output_filter(f);
  +                return ap_pass_brigade(f->next, bb);
  +            }
           }
  -
           /* We're cool with filtering this. */
           ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
           ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
  @@ -840,10 +845,228 @@
       return APR_SUCCESS;
   }
   
  +
  +/* Filter to inflate for a content-transforming proxy.  */
  +static apr_status_t inflate_out_filter(ap_filter_t *f,
  +                                      apr_bucket_brigade *bb)
  +{
  +    /* have we read the zlib header in yet? assume we have in a previous pass */
  +    int deflate_init = 1; 
  +    apr_bucket *bkt;
  +    request_rec *r = f->r;
  +    deflate_ctx *ctx = f->ctx;
  +    int zRC;
  +    apr_status_t rv;
  +    deflate_filter_config *c;
  +
  +    c = ap_get_module_config(r->server->module_config, &deflate_module);
  +
  +    if (!ctx) {
  +        int found = 0;
  +        char *token, deflate_hdr[10];
  +        const char *encoding;
  +        apr_size_t len;
  +
  +        /* only work on main request/no subrequests */
  +        if (r->main) {
  +            ap_remove_output_filter(f);
  +            return ap_pass_brigade(f->next, bb);
  +        }
  +
  +        /* Let's see what our current Content-Encoding is.
  +         * If gzip is present, don't gzip again.  (We could, but let's not.)
  +         */
  +        encoding = apr_table_get(r->headers_out, "Content-Encoding");
  +        if (encoding) {
  +            const char *tmp = encoding;
  +
  +            token = ap_get_token(r->pool, &tmp, 0);
  +            while (token && token[0]) {
  +                if (!strcasecmp(token, "gzip")) {
  +                    found = 1;
  +                    break;
  +                }
  +                /* Otherwise, skip token */
  +                tmp++;
  +                token = ap_get_token(r->pool, &tmp, 0);
  +            }
  +        }
  +
  +        if (found == 0) {
  +            ap_remove_output_filter(f);
  +            return ap_pass_brigade(f->next, bb);
  +        }
  +
  +        f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
  +        ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
  +        ctx->buffer = apr_palloc(r->pool, c->bufferSize);
  +
  +
  +        zRC = inflateInit2(&ctx->stream, c->windowSize);
  +
  +        if (zRC != Z_OK) {
  +            f->ctx = NULL;
  +            inflateEnd(&ctx->stream);
  +            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
  +                          "unable to init Zlib: "
  +                          "inflateInit2 returned %d: URL %s",
  +                          zRC, r->uri);
  +            ap_remove_output_filter(f);
  +            return ap_pass_brigade(f->next, bb);
  +        }
  +
  +        /* initialize deflate output buffer */
  +        ctx->stream.next_out = ctx->buffer;
  +        ctx->stream.avail_out = c->bufferSize;
  +
  +        deflate_init = 0;
  +    }
  +
  +    for (bkt = APR_BRIGADE_FIRST(bb);
  +         bkt != APR_BRIGADE_SENTINEL(bb);
  +         bkt = APR_BUCKET_NEXT(bkt))
  +    {
  +        const char *data;
  +        apr_size_t len;
  +
  +        /* If we actually see the EOS, that means we screwed up! */
  +        if (APR_BUCKET_IS_EOS(bkt)) {
  +            inflateEnd(&ctx->stream);
  +            return APR_EGENERAL;
  +        }
  +
  +        if (APR_BUCKET_IS_FLUSH(bkt)) {
  +            apr_bucket *tmp_heap;
  +            zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
  +            if (zRC != Z_OK) {
  +                inflateEnd(&ctx->stream);
  +                return APR_EGENERAL;
  +            }
  +
  +            ctx->stream.next_out = ctx->buffer;
  +            len = c->bufferSize - ctx->stream.avail_out;
  +
  +            ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
  +            tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
  +                                             NULL, f->c->bucket_alloc);
  +            APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
  +            ctx->stream.avail_out = c->bufferSize;
  +
  +            /* Move everything to the returning brigade. */
  +            APR_BUCKET_REMOVE(bkt);
  +            break;
  +        }
  +
  +        /* read */
  +        apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
  +
  +        /* first bucket contains zlib header */
  +        if ( ! deflate_init++ ) {
  +            if ( len < 10 ) {
  +                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
  +                              "Insufficient data for inflate");
  +                return APR_EGENERAL ;
  +            } 
  +            else  {
  +                if (data[0] != deflate_magic[0] ||
  +                    data[1] != deflate_magic[1]) {
  +                        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
  +                                      "deflate: bad header");
  +                    return APR_EGENERAL ;
  +                }
  +                data += 10 ;
  +                len -= 10 ;
  +           }
  +        }
  +
  +        /* pass through zlib inflate. */
  +        ctx->stream.next_in = (unsigned char *)data;
  +        ctx->stream.avail_in = len;
  +
  +        zRC = Z_OK;
  +
  +        while (ctx->stream.avail_in != 0) {
  +            if (ctx->stream.avail_out == 0) {
  +                apr_bucket *tmp_heap;
  +                ctx->stream.next_out = ctx->buffer;
  +                len = c->bufferSize - ctx->stream.avail_out;
  +
  +                ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
  +                tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
  +                                                  NULL, f->c->bucket_alloc);
  +                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
  +                ctx->stream.avail_out = c->bufferSize;
  +            }
  +
  +            zRC = inflate(&ctx->stream, Z_NO_FLUSH);
  +
  +            if (zRC == Z_STREAM_END) {
  +                break;
  +            }
  +
  +            if (zRC != Z_OK) {
  +                    inflateEnd(&ctx->stream);
  +                    return APR_EGENERAL;
  +            }
  +        }
  +        if (zRC == Z_STREAM_END) {
  +            apr_bucket *tmp_heap, *eos;
  +
  +            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
  +                          "Zlib: Inflated %ld to %ld : URL %s",
  +                          ctx->stream.total_in, ctx->stream.total_out,
  +                          r->uri);
  +
  +            len = c->bufferSize - ctx->stream.avail_out;
  +
  +            ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
  +            tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
  +                                              NULL, f->c->bucket_alloc);
  +            APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
  +            ctx->stream.avail_out = c->bufferSize;
  +
  +            /* Is the remaining 8 bytes already in the avail stream? */
  +            if (ctx->stream.avail_in >= 8) {
  +                unsigned long compCRC, compLen;
  +                compCRC = getLong(ctx->stream.next_in);
  +                if (ctx->crc != compCRC) {
  +                    inflateEnd(&ctx->stream);
  +                    return APR_EGENERAL;
  +                }
  +                ctx->stream.next_in += 4;
  +                compLen = getLong(ctx->stream.next_in);
  +                if (ctx->stream.total_out != compLen) {
  +                    inflateEnd(&ctx->stream);
  +                    return APR_EGENERAL;
  +                }
  +            }
  +            else {
  +                /* FIXME: We need to grab the 8 verification bytes
  +                 * from the wire! */
  +                inflateEnd(&ctx->stream);
  +                return APR_EGENERAL;
  +            }
  +
  +            inflateEnd(&ctx->stream);
  +
  +            eos = apr_bucket_eos_create(f->c->bucket_alloc);
  +            APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos); 
  +            break;
  +        }
  +
  +    }
  +
  +    rv = ap_pass_brigade(f->next, ctx->proc_bb);
  +    apr_brigade_cleanup(ctx->proc_bb);
  +    return rv ;
  +}
  +
   static void register_hooks(apr_pool_t *p)
   {
       ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL,
                                 AP_FTYPE_CONTENT_SET);
  +    ap_register_output_filter("INFLATE", inflate_out_filter, NULL,
  +                              AP_FTYPE_RESOURCE-1);
       ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL,
                                 AP_FTYPE_CONTENT_SET);
   }