static ngx_int_t
ngx_http_srcache_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_http_srcache_ctx_t      *ctx, *pr_ctx;
    ngx_int_t                    rc;
    ngx_str_t                    skip;
    ngx_chain_t                 *cl;
    ngx_http_srcache_loc_conf_t *slcf;
    size_t                       len;
    unsigned                     last;

    dd_enter();

    if (in == NULL) {
        return ngx_http_srcache_next_body_filter(r, NULL);
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_srcache_filter_module);

    if (ctx == NULL || ctx->from_cache || ctx->store_skip) {
        dd("bypass: %.*s", (int) r->uri.len, r->uri.data);
        return ngx_http_srcache_next_body_filter(r, in);
    }

    if (ctx->ignore_body || ctx->in_store_subrequest/* || ctx->fetch_error */) {
        dd("ignore body: ignore body %d, in store sr %d",
           (int) ctx->ignore_body, (int) ctx->in_store_subrequest);
        ngx_http_srcache_discard_bufs(r->pool, in);
        return NGX_OK;
    }

    if (ctx->in_fetch_subrequest) {
        if (ctx->parsing_cached_headers) {

            /* parse the cached response's headers and
             * set r->parent->headers_out */

            if (ctx->process_header == NULL) {
                dd("restore parent request header");
                ctx->process_header = ngx_http_srcache_process_status_line;
                r->state = 0; /* sw_start */
            }

            for (cl = in; cl; cl = cl->next) {
                if (ngx_buf_in_memory(cl->buf)) {
                    dd("old pos %p, last %p", cl->buf->pos, cl->buf->last);

                    rc = ctx->process_header(r, cl->buf);

                    if (rc == NGX_AGAIN) {
                        dd("AGAIN/OK: new pos %p, last %p",
                           cl->buf->pos, cl->buf->last);

                        continue;
                    }

                    if (rc == NGX_ERROR) {
                        r->state = 0; /* sw_start */
                        ctx->parsing_cached_headers = 0;
                        ctx->ignore_body = 1;
                        ngx_http_srcache_discard_bufs(r->pool, cl);
                        pr_ctx = ngx_http_get_module_ctx(r->parent,
                                              ngx_http_srcache_filter_module);

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

                        pr_ctx->from_cache = 0;

                        return NGX_OK;
                    }

                    /* rc == NGX_OK */

                    dd("OK: new pos %p, last %p", cl->buf->pos, cl->buf->last);
                    dd("buf left: %.*s", (int) (cl->buf->last - cl->buf->pos),
                       cl->buf->pos);

                    ctx->parsing_cached_headers = 0;

                    break;
                }
            }

            if (cl == NULL) {
                return NGX_OK;
            }

            if (cl->buf->pos == cl->buf->last) {
                cl = cl->next;
            }

            if (cl == NULL) {
                return NGX_OK;
            }

            in = cl;
        }

        dd("save the cached response body for parent");

        pr_ctx = ngx_http_get_module_ctx(r->parent,
                                         ngx_http_srcache_filter_module);

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

        rc = ngx_http_srcache_add_copy_chain(r->pool,
                                             &pr_ctx->body_from_cache, in,
                                             &last);

        if (rc != NGX_OK) {
            return NGX_ERROR;
        }

        if (last) {
            ctx->seen_subreq_eof = 1;
        }

        ngx_http_srcache_discard_bufs(r->pool, in);

        return NGX_OK;
    }

    if (ctx->store_response) {
        dd("storing the response: %p", in);

        if (ctx->response_length == 0) {
            /* store the response header to ctx->body_to_cache */
            if (ngx_http_srcache_store_response_header(r, ctx) == NGX_ERROR) {
                return NGX_ERROR;
            }

            if (ngx_http_srcache_next_header_filter(r) == NGX_ERROR) {
                return NGX_ERROR;
            }
        }

        for (cl = in; cl; cl = cl->next) {
            if (ngx_buf_in_memory(cl->buf)) {
                len = ngx_buf_size(cl->buf);
                ctx->response_length += len;
                ctx->response_body_length += len;
            }
        }

        slcf = ngx_http_get_module_loc_conf(r, ngx_http_srcache_filter_module);

        if (slcf->store_max_size != 0
            && ctx->response_length > slcf->store_max_size)
        {
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "srcache_store bypassed because response body "
                           "exceeded maximum size: %z (limit is: %z)",
                           ctx->response_length, slcf->store_max_size);

            ctx->store_response = 0;

            goto done;
        }

        rc = ngx_http_srcache_add_copy_chain(r->pool, &ctx->body_to_cache,
                                             in, &last);

        if (rc != NGX_OK) {
            ctx->store_response = 0;
            goto done;
        }

        if (last && r == r->main) {

#if 1
            if (r->headers_out.content_length_n >
                (off_t) ctx->response_body_length)
            {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "srcache_store: skipped because response body "
                              "truncated: %O > %uz",
                              r->headers_out.content_length_n,
                              ctx->response_body_length);

                ctx->store_response = 0;
                goto done;
            }

            if (r->headers_out.status >= NGX_HTTP_SPECIAL_RESPONSE
                && r->headers_out.status != ctx->http_status)
            {
                /* data truncation or body receive timeout */

                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "srcache_store: skipped due to new error status "
                              "code %ui (old: %ui)",
                              r->headers_out.status, ctx->http_status);

                ctx->store_response = 0;
                goto done;
            }
#endif

            if (slcf->store_skip != NULL
                && ngx_http_complex_value(r, slcf->store_skip, &skip) == NGX_OK
                && skip.len
                && (skip.len != 1 || skip.data[0] != '0'))
            {
                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                               "srcache_store skipped due to the true value in "
                               "srcache_store_skip: \"%V\"", &skip);

                ctx->store_response = 0;
                goto done;
            }

            rc = ngx_http_srcache_store_subrequest(r, ctx);

            if (rc != NGX_OK) {
                ctx->store_response = 0;
                goto done;
            }
        }

    } else {
        dd("NO store response");
    }

done:
    return ngx_http_srcache_next_body_filter(r, in);
}
static ngx_int_t
ngx_http_srcache_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_http_srcache_ctx_t      *ctx, *pr_ctx;
    ngx_int_t                    rc;
    ngx_chain_t                 *cl;
    ngx_flag_t                   last;
    ngx_http_srcache_loc_conf_t *slcf;

    dd_enter();

    if (in == NULL) {
        return ngx_http_srcache_next_body_filter(r, NULL);
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_srcache_filter_module);

    if (ctx == NULL || ctx->from_cache || ctx->store_skip) {
        dd("bypass: %.*s", (int) r->uri.len, r->uri.data);
        return ngx_http_srcache_next_body_filter(r, in);
    }

    if (ctx->ignore_body || ctx->in_store_subrequest/* || ctx->fetch_error */) {
        dd("ignore body: ignore body %d, in store sr %d",
                (int) ctx->ignore_body, (int) ctx->in_store_subrequest);
        ngx_http_srcache_discard_bufs(r->pool, in);
        return NGX_OK;
    }

    if (ctx->in_fetch_subrequest) {
        if (ctx->parsing_cached_headers) {

            /* parse the cached response's headers and
             * set r->parent->headers_out */

            if (ctx->process_header == NULL) {
                dd("restore parent request header");
                ctx->process_header = ngx_http_srcache_process_status_line;
                r->state = 0; /* sw_start */
            }

            for (cl = in; cl; cl = cl->next) {
                if (ngx_buf_in_memory(cl->buf)) {
                    dd("old pos %p, last %p", cl->buf->pos, cl->buf->last);

                    rc = ctx->process_header(r, cl->buf);

                    if (rc == NGX_AGAIN) {
                        dd("AGAIN/OK: new pos %p, last %p",
                                cl->buf->pos, cl->buf->last);

                        continue;
                    }

                    if (rc == NGX_ERROR) {
                        r->state = 0; /* sw_start */
                        ctx->parsing_cached_headers = 0;
                        ctx->ignore_body = 1;
                        ngx_http_srcache_discard_bufs(r->pool, cl);
                        pr_ctx = ngx_http_get_module_ctx(r->parent,
                                ngx_http_srcache_filter_module);

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

                        pr_ctx->from_cache = 0;

                        return NGX_OK;
                    }

                    /* rc == NGX_OK */

                    dd("OK: new pos %p, last %p", cl->buf->pos, cl->buf->last);
                    dd("buf left: %.*s", (int) (cl->buf->last - cl->buf->pos),
                            cl->buf->pos);

                    ctx->parsing_cached_headers = 0;

                    break;
                }
            }

            if (cl == NULL) {
                return NGX_OK;
            }

            if (cl->buf->pos == cl->buf->last) {
                cl = cl->next;
            }

            if (cl == NULL) {
                return NGX_OK;
            }

            in = cl;
        }

        dd("save the cached response body for parent");

        pr_ctx = ngx_http_get_module_ctx(r->parent,
                ngx_http_srcache_filter_module);

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

        rc = ngx_http_srcache_add_copy_chain(r->pool,
                &pr_ctx->body_from_cache, in);

        if (rc != NGX_OK) {
            return NGX_ERROR;
        }

        ngx_http_srcache_discard_bufs(r->pool, in);

        return NGX_OK;
    }

    if (ctx->store_response) {
        dd("storing the response: %p", in);

        if (ctx->response_length == 0) {
            /* store the response header to ctx->body_to_cache */
            rc = ngx_http_srcache_store_response_header(r, ctx);
            if (rc == NGX_ERROR) {
                return NGX_ERROR;
            }
        }

        last = 0;

        for (cl = in; cl; cl = cl->next) {
            if (ngx_buf_in_memory(cl->buf)) {
                ctx->response_length += ngx_buf_size(cl->buf);
            }

            if (cl->buf->last_buf) {
                last = 1;
                break;
            }
        }

        slcf = ngx_http_get_module_loc_conf(r, ngx_http_srcache_filter_module);

        if (slcf->store_max_size != 0
                && ctx->response_length > slcf->store_max_size)
        {
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "srcache_store bypassed because response body exceeded "
                    "maximum size: %z (limit is: %z)",
                    ctx->response_length, slcf->store_max_size);

            ctx->store_response = 0;

            goto done;
        }

        rc = ngx_http_srcache_add_copy_chain(r->pool, &ctx->body_to_cache, in);

        if (rc != NGX_OK) {
            ctx->store_response = 0;
            goto done;
        }

        if (last && r == r->main) {
            rc = ngx_http_srcache_store_subrequest(r, ctx);

            if (rc != NGX_OK) {
                ctx->store_response = 0;
                goto done;
            }
        }
    } else {
        dd("NO store response");
    }

done:
    return ngx_http_srcache_next_body_filter(r, in);
}