/* * Copyright (C) Yichun Zhang (agentzh) */ #ifndef DDEBUG #define DDEBUG 0 #endif #include "ddebug.h" #include "ngx_http_srcache_store.h" #include "ngx_http_srcache_fetch.h" #include "ngx_http_srcache_util.h" #include static ngx_int_t ngx_http_srcache_store_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc); static ngx_int_t ngx_http_srcache_store_subrequest(ngx_http_request_t *r, ngx_http_srcache_ctx_t *ctx); static ngx_int_t ngx_http_srcache_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_srcache_body_filter(ngx_http_request_t *r, ngx_chain_t *in); ngx_http_output_header_filter_pt ngx_http_srcache_next_header_filter; ngx_http_output_body_filter_pt ngx_http_srcache_next_body_filter; static ngx_int_t ngx_http_srcache_header_filter(ngx_http_request_t *r) { ngx_http_srcache_ctx_t *ctx, *pr_ctx; ngx_http_srcache_loc_conf_t *slcf; ngx_http_post_subrequest_t *ps; ngx_str_t skip; ngx_uint_t *sp; /* status pointer */ dd_enter(); dd("srcache header filter"); ctx = ngx_http_get_module_ctx(r, ngx_http_srcache_filter_module); if (r != r->main && ctx == NULL) { ps = r->post_subrequest; if (ps != NULL && (ps->handler == ngx_http_srcache_fetch_post_subrequest || ps->handler == ngx_http_srcache_store_post_subrequest) && ps->data != NULL) { /* the subrequest ctx has been cleared by * ngx_http_internal_redirect, resume it from the post_subrequest * data */ dd("resumed ctx from post_subrequest"); ctx = ps->data; ngx_http_set_ctx(r, ctx, ngx_http_srcache_filter_module); } } if (ctx == NULL || ctx->from_cache) { dd("bypass: %.*s", (int) r->uri.len, r->uri.data); return ngx_http_srcache_next_header_filter(r); } if (ctx->in_fetch_subrequest) { dd("in fetch subrequest"); pr_ctx = ngx_http_get_module_ctx(r->parent, ngx_http_srcache_filter_module); if (pr_ctx == NULL) { dd("parent ctx is null"); ctx->ignore_body = 1; return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_fetch: subrequest returned status %ui", r->headers_out.status); if (r->headers_out.status != NGX_HTTP_OK) { dd("ignoring body because status == %d", (int) r->headers_out.status); ctx->ignore_body = 1; pr_ctx->waiting_subrequest = 0; /* pr_ctx->fetch_error = 1; */ r->header_sent = 1; if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } return NGX_OK; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_fetch decides to send the response in cache"); r->filter_need_in_memory = 1; pr_ctx->from_cache = 1; ctx->parsing_cached_headers = 1; r->header_sent = 1; if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } return NGX_OK; } if (ctx->in_store_subrequest) { dd("in store subreuqest"); ctx->ignore_body = 1; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store: subrequest returned status %ui", r->headers_out.status); r->header_sent = 1; if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } return NGX_OK; } slcf = ngx_http_get_module_loc_conf(r, ngx_http_srcache_filter_module); if (slcf->store == NULL) { dd("slcf->store is NULL"); return ngx_http_srcache_next_header_filter(r); } /* slcf->store != NULL */ #if 1 if (!(r->method & (slcf->cache_methods & ~NGX_HTTP_HEAD))) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store skipped due to request method %V", &r->method_name); return ngx_http_srcache_next_header_filter(r); } #endif if (!slcf->ignore_content_encoding && r->headers_out.content_encoding && r->headers_out.content_encoding->value.len) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "srcache_store skipped due to response header " "\"Content-Encoding: %V\" (maybe you forgot to disable " "compression on the backend?)", &r->headers_out.content_encoding->value); return ngx_http_srcache_next_header_filter(r); } if (slcf->resp_cache_control && ngx_http_srcache_response_no_cache(r, slcf, ctx) == NGX_OK) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store skipped due to response header " "Cache-Control"); return ngx_http_srcache_next_header_filter(r); } 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 fed into " "srcache_store_skip: \"%V\"", &skip); ctx->store_skip = 1; return ngx_http_srcache_next_header_filter(r); } dd("error page: %d", (int) r->error_page); if (slcf->store_statuses != NULL) { sp = (ngx_uint_t *) slcf->store_statuses; while (r->headers_out.status < *sp) { sp++; } if (*sp == 0 || r->headers_out.status > *sp) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store bypassed because of unmatched " "status code %ui with srcache_store_statuses", r->headers_out.status); return ngx_http_srcache_next_header_filter(r); } /* r->headers_out.status == (ngx_uint_t) *sp */ } else { if (r->headers_out.status != NGX_HTTP_OK && r->headers_out.status != NGX_HTTP_MOVED_TEMPORARILY && r->headers_out.status != NGX_HTTP_MOVED_PERMANENTLY) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store bypassed because of unmatched status " "code %i (only 200, 301, or 302 are accepted by " "default)", r->headers_out.status); return ngx_http_srcache_next_header_filter(r); } } if (slcf->store_max_size != 0 && r->headers_out.content_length_n > 0 && r->headers_out.content_length_n + 15 /* just an approxiation for the response header size */ > (off_t) slcf->store_max_size) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store bypassed because of too large " "Content-Length response header: %O (limit is: %z)", r->headers_out.content_length_n, slcf->store_max_size); return ngx_http_srcache_next_header_filter(r); } dd("try to save the response header"); if (r != r->main) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_srcache not working in subrequests (yet)"); /* not allowd in subrquests */ return NGX_ERROR; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "srcache_store decides to store the response"); r->filter_need_in_memory = 1; ctx->http_status = r->headers_out.status; ctx->store_response = 1; if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } /* store the response header to ctx->body_to_cache */ if (ngx_http_srcache_store_response_header(r, ctx) == NGX_ERROR) { return NGX_ERROR; } return ngx_http_srcache_next_header_filter(r); } 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); slcf = ngx_http_get_module_loc_conf(r, ngx_http_srcache_filter_module); if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT && ctx->http_status == NGX_HTTP_OK) { u_char *p; if (!slcf->store_ranges) { ctx->store_response = 0; goto done; } dd("fix 206 status code"); /* handle 206 Partial Content generated by the range filter */ cl = ctx->body_to_cache; assert(cl && cl->buf && cl->buf->last - cl->buf->pos > 12); p = cl->buf->pos + sizeof("HTTP/1.x 20") - 1; *p = '6'; ctx->http_status = NGX_HTTP_PARTIAL_CONTENT; } 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; } } 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_store_subrequest(ngx_http_request_t *r, ngx_http_srcache_ctx_t *ctx) { ngx_http_srcache_ctx_t *sr_ctx; ngx_str_t args; ngx_uint_t flags = 0; ngx_http_request_t *sr; ngx_int_t rc; ngx_http_request_body_t *rb = NULL; ngx_http_srcache_loc_conf_t *conf; ngx_http_post_subrequest_t *psr; ngx_http_srcache_parsed_request_t *parsed_sr; dd("store subrequest"); conf = ngx_http_get_module_loc_conf(r, ngx_http_srcache_filter_module); if (conf->store == NULL) { dd("conf store is NULL"); return NGX_ERROR; } parsed_sr = ngx_palloc(r->pool, sizeof(ngx_http_srcache_parsed_request_t)); if (parsed_sr == NULL) { return NGX_ERROR; } parsed_sr->method = conf->store->method; parsed_sr->method_name = conf->store->method_name; if (ctx->body_to_cache) { dd("found body to cache (len %d)", (int) ctx->response_length); rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); if (rb == NULL) { return NGX_ERROR; } rb->bufs = ctx->body_to_cache; rb->buf = ctx->body_to_cache->buf; parsed_sr->request_body = rb; } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "srcache_store: no request body for the subrequest"); return NGX_ERROR; } parsed_sr->content_length_n = ctx->response_length; if (ngx_http_complex_value(r, &conf->store->location, &parsed_sr->location) != NGX_OK) { return NGX_ERROR; } if (parsed_sr->location.len == 0) { return NGX_ERROR; } if (ngx_http_complex_value(r, &conf->store->args, &parsed_sr->args) != NGX_OK) { return NGX_ERROR; } args.data = NULL; args.len = 0; if (ngx_http_parse_unsafe_uri(r, &parsed_sr->location, &args, &flags) != NGX_OK) { return NGX_ERROR; } if (args.len > 0 && parsed_sr->args.len == 0) { parsed_sr->args = args; } dd("firing the store subrequest"); dd("store location: %.*s", (int) parsed_sr->location.len, parsed_sr->location.data); dd("store args: %.*s", (int) parsed_sr->args.len, parsed_sr->args.data); sr_ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_srcache_ctx_t)); if (sr_ctx == NULL) { return NGX_ERROR; } sr_ctx->in_store_subrequest = 1; psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (psr == NULL) { return NGX_ERROR; } psr->handler = ngx_http_srcache_store_post_subrequest; psr->data = sr_ctx; rc = ngx_http_subrequest(r, &parsed_sr->location, &parsed_sr->args, &sr, psr, flags); if (rc != NGX_OK) { return NGX_ERROR; } rc = ngx_http_srcache_adjust_subrequest(sr, parsed_sr); if (rc != NGX_OK) { return NGX_ERROR; } ngx_http_set_ctx(sr, sr_ctx, ngx_http_srcache_filter_module); return NGX_OK; } static ngx_int_t ngx_http_srcache_store_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc) { if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE || r->headers_out.status >= NGX_HTTP_SPECIAL_RESPONSE) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "srcache_store subrequest failed: rc=%i status=%ui", rc, r->headers_out.status); } return NGX_OK; } ngx_int_t ngx_http_srcache_filter_init(ngx_conf_t *cf) { ngx_http_srcache_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_srcache_header_filter; ngx_http_srcache_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_srcache_body_filter; return NGX_OK; } /* vi:set ft=c ts=4 sw=4 et fdm=marker: */