static ngx_int_t
ngx_http_reqstat_input_body_filter(ngx_http_request_t *r, ngx_buf_t *buf)
{
    ngx_uint_t                    diff;
    ngx_int_t                     rc;
    ngx_http_reqstat_conf_t      *slcf;
    ngx_http_reqstat_store_t     *store;

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_reqstat_module);

    if (slcf->monitor == NULL) {
        return ngx_http_next_input_body_filter(r, buf);
    }
    
    store = ngx_pcalloc(r->pool, sizeof(ngx_http_reqstat_store_t));
    if (store == NULL) {
        return NGX_ERROR;
    }
  
    rc = ngx_http_test_predicates(r, slcf->bypass);
    if(rc == NGX_ERROR) {
        return NGX_ERROR;
    } else if(rc == NGX_DECLINED) {
        store->bypass = 1;
    }
    
    ngx_http_set_ctx(r, store, ngx_http_reqstat_module);

    if (store->bypass) {
        return ngx_http_next_input_body_filter(r, buf);
    }

    diff = r->connection->received - store->recv;
    store->recv = r->connection->received;
    store->bytes_in = diff;

    return ngx_http_next_input_body_filter(r, buf);

}
static ngx_http_reqstat_store_t *
ngx_http_reqstat_create_store(ngx_http_request_t *r,
    ngx_http_reqstat_conf_t *rlcf)
{
    ngx_str_t                     val;
    ngx_uint_t                    i;
    ngx_shm_zone_t              **shm_zone, *z;
    ngx_http_reqstat_ctx_t       *ctx;
    ngx_http_reqstat_store_t     *store;
    ngx_http_reqstat_rbnode_t    *fnode, **fnode_store;

    store = ngx_pcalloc(r->pool, sizeof(ngx_http_reqstat_store_t));
    if (store == NULL) {
        return NULL;
    }

    if (rlcf->monitor == NULL) {
        store->bypass = 1;
        return store;
    }

    store->conf = rlcf;

    switch (ngx_http_test_predicates(r, rlcf->bypass)) {

    case NGX_ERROR:
        return NULL;

    case NGX_DECLINED:
        store->bypass = 1;
        return store;

    default: /* NGX_OK */
        break;
    }

    if (ngx_array_init(&store->monitor_index, r->pool, rlcf->monitor->nelts,
                       sizeof(ngx_http_reqstat_rbnode_t *)) == NGX_ERROR)
    {
        return NULL;
    }

    shm_zone = rlcf->monitor->elts;
    for (i = 0; i < rlcf->monitor->nelts; i++) {
        z = shm_zone[i];
        ctx = z->data;

        if (ngx_http_complex_value(r, &ctx->value, &val) != NGX_OK) {
            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                          "failed to reap the key \"%V\"", ctx->val);
            continue;
        }

        fnode = ngx_http_reqstat_rbtree_lookup(shm_zone[i], &val);

        if (fnode == NULL) {
            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                          "failed to alloc node in zone \"%V\", "
                          "enlarge it please",
                          &z->shm.name);

        } else {
            fnode_store = ngx_array_push(&store->monitor_index);
            *fnode_store = fnode;
        }
    }

    return store;
}
static ngx_int_t
ngx_http_filter_cache_header_filter(ngx_http_request_t *r)
{
    ngx_http_filter_cache_ctx_t *ctx = NULL;
    ngx_http_filter_cache_conf_t *conf = NULL;
    time_t  now, valid;
    ngx_temp_file_t *tf;
    ngx_chain_t   out;
    ssize_t offset;
    ngx_list_part_t *part;
    ngx_table_elt_t *h;
    ngx_uint_t i;
    u_char *p;
    size_t len;
    ngx_pool_cleanup_t *cln = NULL;

    ngx_http_filter_cache_meta_t meta;

    if(r != r->main) {
        /*just skip as we got headers in main*/
        return ngx_http_next_header_filter(r);
    }

    conf = ngx_http_get_module_loc_conf(r, ngx_http_filter_cache_module);

    switch (ngx_http_test_predicates(r, conf->upstream.no_cache)) {
    case NGX_ERROR:
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, __FILE__" ngx_http_test_predicates returned an error for no_cache");
        return NGX_ERROR;
    case NGX_DECLINED:
        goto nocache;
    default: /* NGX_OK */
        break;
    }

    ctx = r->filter_cache;

    if(!ctx || (FILTER_DONOTCACHE == ctx->cacheable)) {
        goto nocache;
    }
    /* ngx_http_filter_cache_create(r); */

    if (ctx->cache && ctx->cache->file.fd != NGX_INVALID_FILE) {
        ngx_pool_run_cleanup_file(r->pool, ctx->cache->file.fd);
        ctx->cache->file.fd = NGX_INVALID_FILE;
    }

    ctx->cache->valid_sec = 0;

    now = ngx_time();

    valid = 0;
    valid = ngx_http_filter_cache_valid(conf->upstream.cache_valid,
                                      r->headers_out.status);
    if (valid) {
        ctx->cache->valid_sec = now + valid;
    } else {
        goto nocache;
    }

    tf =  ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));

    if (tf == NULL) {
        return NGX_ERROR;
    }

    tf->file.fd = NGX_INVALID_FILE;
    tf->file.log = r->connection->log;
    tf->path = conf->upstream.temp_path;
    tf->pool = r->pool;
    tf->persistent = 1;

    if (ngx_create_temp_file(&tf->file, tf->path, tf->pool,
                             tf->persistent, tf->clean, tf->access)
        != NGX_OK) {
        return NGX_ERROR;
    }
    ctx->tf = tf;

    cln = ngx_pool_cleanup_add(r->pool, 0);
    if (cln == NULL) {
        return NGX_ERROR;
    }
    cln->handler = filter_cache_cleanup;
    cln->data = ctx;

    ctx->buffer.pos = ctx->buffer.start = ngx_palloc(r->pool, conf->upstream.buffer_size);
    ctx->buffer.end = ctx->buffer.start + conf->upstream.buffer_size;
    ctx->buffer.temporary = 1;
    ctx->buffer.memory = 1;
    ctx->buffer.last_buf = 1;

    ctx->buffer.pos += ctx->cache->header_start;

    ctx->cache->last_modified = r->headers_out.last_modified_time;
    ctx->cache->date = now;

    /* Headers */

    /* fill in the metadata*/
    meta.status = r->headers_out.status;

#if (NGX_HTTP_GZIP)
    meta.gzip_vary = r->gzip_vary; /* Note: there is still some wierdness to how gzip_vary works...*/
#endif

    meta.last_modified_time = r->headers_out.last_modified_time;

    ngx_memcpy((void *)(ctx->buffer.pos), (void *)(&meta), sizeof(ngx_http_filter_cache_meta_t) );
    ctx->buffer.pos += sizeof(ngx_http_filter_cache_meta_t);

    /* Headers taht aren't in teh table for some reason */

    /*Do we need to try to set it if it's not set???*/
    /* Content Type */
    if ( r->headers_out.content_type.data ) {
        p = memchr((void *)r->headers_out.content_type.data, ';', r->headers_out.content_type.len );
        if ( p ) {
            len = p - r->headers_out.content_type.data;
            ngx_cpystrn( ctx->buffer.pos, r->headers_out.content_type.data, len + 1);
            ctx->buffer.pos += len + 1;
        }
        else {
            ngx_cpystrn( ctx->buffer.pos, r->headers_out.content_type.data, r->headers_out.content_type.len + 1 );
            ctx->buffer.pos += r->headers_out.content_type.len + 1;
        }
    }
    else {
        *ctx->buffer.pos = (u_char)'\0';
        ctx->buffer.pos++;
    }

    /* Charset */
    if ( r->headers_out.charset.data ) {
        ngx_cpystrn( ctx->buffer.pos, r->headers_out.charset.data, r->headers_out.charset.len + 1 );
        ctx->buffer.pos += r->headers_out.charset.len + 1;
    }
    else {
        *ctx->buffer.pos = (u_char)'\0';
        ctx->buffer.pos++;
    }

    /* Content Encoding */
    if ( r->headers_out.content_encoding && r->headers_out.content_encoding->value.len) {
        ngx_cpystrn( ctx->buffer.pos, r->headers_out.content_encoding->value.data, r->headers_out.content_encoding->value.len + 1 );
        ctx->buffer.pos += r->headers_out.content_encoding->value.len + 1;
    }
    else {
        *ctx->buffer.pos = (u_char)'\0';
        ctx->buffer.pos++;
    }

    /* Last-Modified */
    if(r->headers_out.last_modified_time && r->headers_out.last_modified && r->headers_out.last_modified->value.len) {
        ngx_cpystrn( ctx->buffer.pos, r->headers_out.last_modified->value.data, r->headers_out.last_modified->value.len + 1 );
        ctx->buffer.pos += r->headers_out.last_modified->value.len + 1;
    } else {
        *ctx->buffer.pos = (u_char)'\0';
        ctx->buffer.pos++;
    }


    /* XXX: is last-modified special???*/
    /* Everything From the Table */
    part = &r->headers_out.headers.part;
    h = part->elts;
    for (i=0; /* void */; i++) {
        if ( i >= part->nelts || !part->nelts ) {
            if ( part->next == NULL ) {
                ctx->cacheable = FILTER_CACHEABLE;
                break;
            }
            part = part->next;
            h = part->elts;
            i = 0;
        }

        /*need to be really sure this header is "valid"*/
        /* if(h[i].key.len && h[i].value.len && h[i].hash && h[i].lowcase_key) {*/

            /* if(!h[i].lowcase_key) { */
            /*     if((h[i].lowcase_key = ngx_pnalloc(r->pool, h->key.len +1)) == NULL) { */
            /*         continue; */
            /*     } */
            /*     ngx_strlow(h[i].lowcase_key, h[i].key.data, h[i].key.len); */
            /* } */

            /* if(!h[i].hash) { */
            /*     h[i].hash = ngx_hash_key_lc(h[i].key.data, h[i].key.len); */
            /* } */

            /* if (ngx_hash_find(&conf->upstream.hide_headers_hash, h[i].hash, */
            /*                   h[i].lowcase_key, h[i].key.len)) */
            /* { */
            /*     continue; */
            /* } */

        if(h[i].key.len && h[i].value.len) {
            if(find_string_in_array(&(h[i].key), conf->upstream.hide_headers)){
                continue;
            }
            if ( (ngx_uint_t)(h[i].key.len + h[i].value.len + 4) > (ngx_uint_t)(ctx->buffer.last - ctx->buffer.pos) ) {
                ctx->cacheable = FILTER_DONOTCACHE;
                break;
            }

            ngx_cpystrn( ctx->buffer.pos, h[i].key.data, h[i].key.len + 1 );
            ctx->buffer.pos += h[i].key.len + 1;

            ngx_cpystrn( ctx->buffer.pos, h[i].value.data, h[i].value.len + 1 );
            ctx->buffer.pos += h[i].value.len + 1;
        }

    }

    if(FILTER_CACHEABLE != ctx->cacheable) {
        goto nocache;
    }

    ctx->buffer.last = ctx->buffer.pos;

    ctx->cache->body_start = (u_short) (ctx->buffer.pos - ctx->buffer.start);
    ngx_http_filter_cache_set_header(r, ctx->buffer.start);
    ctx->cache->date = now;

    /*write to temp file*/
    ctx->buffer.pos =  ctx->buffer.start;
    out.buf = &ctx->buffer;
    out.next = NULL;
    offset = ngx_write_chain_to_temp_file(tf, &out);
    tf->offset += offset;


    r->main_filter_need_in_memory = 1;

    return ngx_http_next_header_filter(r);

nocache:

    if(ctx) {
        ctx->cacheable = FILTER_DONOTCACHE;
        /* if(ctx->cache) { */
            /* ngx_http_filter_cache_free(ctx->cache, ctx->tf); */
        /* } */

    }
    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_filter_cache_handler(ngx_http_request_t *r)
{
    ngx_http_filter_cache_ctx_t *ctx = NULL;
    ngx_http_filter_cache_conf_t *conf = NULL;
    ngx_http_variable_value_t      *vv = NULL;
    ngx_http_cache_t  *c = NULL;
    ngx_str_t                    *key = NULL;
    ngx_int_t          rc = NGX_ERROR;

    if(r != r->main) {
        /* we don't currently serve subrequests
         * if we ever do subrequests, we will need a way to associate a ctx with this request
         * maybe keep an r in ctx and compare to r here?
         */
        return cache_miss(r, NULL, 0, 0);
    }

    conf = ngx_http_get_module_loc_conf(r, ngx_http_filter_cache_module);

    /* turned off */
    if (NULL == conf->upstream.cache) {
        return cache_miss(r, NULL, 0, conf->handler);
    }

    ctx = r->filter_cache;

    if(ctx) {
        /*loop detected??*/
        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                      "cache loop in " __FILE__);
        /* XXX: this causes a 598 to be returned.  Is that what we want???
         * should loop return yet another status code??
         * be configurable and default to 598??
         */
        return cache_miss(r, NULL, 0, conf->handler);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_filter_cache_ctx_t));

    if (ctx == NULL) {
        return NGX_ERROR;
    }
    ctx->cache = NULL;
    ctx->cacheable = FILTER_DONOTCACHE;
    ctx->cache_status = NGX_HTTP_CACHE_MISS;

    /* needed so the ctx works in cache status*/
    r->filter_cache = ctx;

    switch (ngx_http_test_predicates(r, conf->upstream.cache_bypass)) {
    case NGX_ERROR:
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, __FILE__" ngx_http_test_predicates returned an error for bypass");
        return NGX_ERROR;
    case NGX_DECLINED:
        ctx->cache_status = NGX_HTTP_CACHE_BYPASS;
        return cache_miss(r, NULL, 0, conf->handler);
    default: /* NGX_OK */
        break;
    }

    if (!(r->method & conf->upstream.cache_methods)) {
        return cache_miss(r, NULL, 0, conf->handler);
    }

    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK && rc != NGX_AGAIN) {
        return rc;
    }

    vv = ngx_http_get_indexed_variable(r, conf->index);

    if (vv == NULL || vv->not_found || vv->len == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "the \"filter_cache_key\" variable is not set");
        return NGX_ERROR;
    }

    ctx->key.data = vv->data;
    ctx->key.len = vv->len;
    c = ctx->cache = NULL;

    if (ngx_http_filter_cache_new(r) != NGX_OK) {
        return NGX_ERROR;
    }

    key = ngx_array_push(&ctx->cache->keys);
    if (key == NULL) {
        return NGX_ERROR;
    }
    key->data = ctx->key.data;
    key->len = ctx->key.len;

    ctx->cache->file_cache = conf->upstream.cache->data;
    ngx_http_filter_cache_create_key(r);
    /* ngx_http_filter_cache_create(r); */

    c = ctx->cache;

    c->min_uses = conf->upstream.cache_min_uses;
    c->body_start = conf->upstream.buffer_size;
    c->file_cache = conf->upstream.cache->data;

    rc = ngx_http_filter_cache_open(r);

    switch(rc) {
    case NGX_HTTP_CACHE_UPDATING:
        if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) {
            if(ctx->cache && conf->grace && ( (ctx->cache->valid_sec - ngx_time() ) < conf->grace)) {
                ctx->cache_status = NGX_HTTP_CACHE_UPDATING;
                rc = NGX_OK;
            } else {
                rc = NGX_HTTP_CACHE_STALE;
            }
        } else {
            rc = NGX_HTTP_CACHE_STALE;
        }
        break;
    case NGX_OK:
        ctx->cache_status = NGX_HTTP_CACHE_HIT;
    }

    switch (rc) {
    case NGX_OK:
        return filter_cache_send(r);
        break;
    case NGX_HTTP_CACHE_STALE:
        ctx->cache_status = NGX_HTTP_CACHE_EXPIRED;
        /*return 599;*/
        break;
    case NGX_DECLINED:
        break;
    case NGX_HTTP_CACHE_SCARCE:
        return cache_miss(r, ctx, 0, conf->handler);
        break;
    case NGX_AGAIN:
        return NGX_BUSY;
    case NGX_ERROR:
        return NGX_ERROR;
    default:
        /*????*/
        break;
    }

    return cache_miss(r, ctx, 1, conf->handler);
}