changeset 5878:2c89956b6a76

Cache: hash of Vary headers now stored in cache. To cache responses with Vary, we now calculate hash of headers listed in Vary, and return the response from cache only if new request headers match. As of now, only one variant of the same resource can be stored in cache.
author Maxim Dounin <mdounin@mdounin.ru>
date Mon, 27 Oct 2014 21:13:58 +0300
parents 60fde1bc7236
children c525c0454aa5
files src/http/ngx_http_cache.h src/http/ngx_http_file_cache.c src/http/ngx_http_upstream.c
diffstat 3 files changed, 153 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/ngx_http_cache.h	Mon Oct 27 21:13:39 2014 +0300
+++ b/src/http/ngx_http_cache.h	Mon Oct 27 21:13:58 2014 +0300
@@ -25,8 +25,9 @@
 
 #define NGX_HTTP_CACHE_KEY_LEN       16
 #define NGX_HTTP_CACHE_ETAG_LEN      42
+#define NGX_HTTP_CACHE_VARY_LEN      42
 
-#define NGX_HTTP_CACHE_VERSION       2
+#define NGX_HTTP_CACHE_VERSION       3
 
 
 typedef struct {
@@ -71,6 +72,8 @@
     time_t                           date;
 
     ngx_str_t                        etag;
+    ngx_str_t                        vary;
+    u_char                           variant[NGX_HTTP_CACHE_KEY_LEN];
 
     size_t                           header_start;
     size_t                           body_start;
@@ -112,6 +115,9 @@
     u_short                          body_start;
     u_char                           etag_len;
     u_char                           etag[NGX_HTTP_CACHE_ETAG_LEN];
+    u_char                           vary_len;
+    u_char                           vary[NGX_HTTP_CACHE_VARY_LEN];
+    u_char                           variant[NGX_HTTP_CACHE_KEY_LEN];
 } ngx_http_file_cache_header_t;
 
 
--- a/src/http/ngx_http_file_cache.c	Mon Oct 27 21:13:39 2014 +0300
+++ b/src/http/ngx_http_file_cache.c	Mon Oct 27 21:13:58 2014 +0300
@@ -29,6 +29,10 @@
     ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key);
 static void ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static void ngx_http_file_cache_vary(ngx_http_request_t *r, u_char *vary,
+    size_t len, u_char *hash);
+static void ngx_http_file_cache_vary_header(ngx_http_request_t *r,
+    ngx_md5_t *md5, ngx_str_t *name);
 static void ngx_http_file_cache_cleanup(void *data);
 static time_t ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache);
 static time_t ngx_http_file_cache_expire(ngx_http_file_cache_t *cache);
@@ -519,6 +523,23 @@
         return NGX_DECLINED;
     }
 
+    if (h->vary_len > NGX_HTTP_CACHE_VARY_LEN) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" has incorrect vary length",
+                      c->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    if (h->vary_len) {
+        ngx_http_file_cache_vary(r, h->vary, h->vary_len, c->variant);
+
+        if (ngx_memcmp(c->variant, h->variant, NGX_HTTP_CACHE_KEY_LEN) != 0) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http file cache vary mismatch");
+            return NGX_DECLINED;
+        }
+    }
+
     c->buf->last += n;
 
     c->valid_sec = h->valid_sec;
@@ -870,6 +891,94 @@
 }
 
 
+static void
+ngx_http_file_cache_vary(ngx_http_request_t *r, u_char *vary, size_t len,
+    u_char *hash)
+{
+    u_char     *p, *last;
+    ngx_str_t   name;
+    ngx_md5_t   md5;
+    u_char      buf[NGX_HTTP_CACHE_VARY_LEN];
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache vary: \"%*s\"", len, vary);
+
+    ngx_md5_init(&md5);
+
+    ngx_strlow(buf, vary, len);
+
+    p = buf;
+    last = buf + len;
+
+    while (p < last) {
+
+        while (p < last && (*p == ' ' || *p == ',')) { p++; }
+
+        name.data = p;
+
+        while (p < last && *p != ',' && *p != ' ') { p++; }
+
+        name.len = p - name.data;
+
+        if (name.len == 0) {
+            break;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http file cache vary: %V", &name);
+
+        ngx_md5_update(&md5, name.data, name.len);
+        ngx_md5_update(&md5, (u_char *) ":", sizeof(":") - 1);
+
+        ngx_http_file_cache_vary_header(r, &md5, &name);
+
+        ngx_md5_update(&md5, (u_char *) CRLF, sizeof(CRLF) - 1);
+    }
+
+    ngx_md5_final(hash, &md5);
+}
+
+
+static void
+ngx_http_file_cache_vary_header(ngx_http_request_t *r, ngx_md5_t *md5,
+    ngx_str_t *name)
+{
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    part = &r->headers_in.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        if (header[i].key.len != name->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(header[i].key.data, name->data, name->len) != 0) {
+            continue;
+        }
+
+        ngx_md5_update(md5, header[i].value.data, header[i].value.len);
+    }
+}
+
+
 void
 ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
 {
@@ -901,6 +1010,19 @@
         ngx_memcpy(h->etag, c->etag.data, c->etag.len);
     }
 
+    if (c->vary.len) {
+        if (c->vary.len > NGX_HTTP_CACHE_VARY_LEN) {
+            /* should not happen */
+            c->vary.len = NGX_HTTP_CACHE_VARY_LEN;
+        }
+
+        h->vary_len = (u_char) c->vary.len;
+        ngx_memcpy(h->vary, c->vary.data, c->vary.len);
+
+        ngx_http_file_cache_vary(r, c->vary.data, c->vary.len, c->variant);
+        ngx_memcpy(h->variant, c->variant, NGX_HTTP_CACHE_KEY_LEN);
+    }
+
     p = buf + sizeof(ngx_http_file_cache_header_t);
 
     p = ngx_cpymem(p, ngx_http_file_cache_key, sizeof(ngx_http_file_cache_key));
@@ -1093,6 +1215,19 @@
         ngx_memcpy(h.etag, c->etag.data, c->etag.len);
     }
 
+    if (c->vary.len) {
+        if (c->vary.len > NGX_HTTP_CACHE_VARY_LEN) {
+            /* should not happen */
+            c->vary.len = NGX_HTTP_CACHE_VARY_LEN;
+        }
+
+        h.vary_len = (u_char) c->vary.len;
+        ngx_memcpy(h.vary, c->vary.data, c->vary.len);
+
+        ngx_http_file_cache_vary(r, c->vary.data, c->vary.len, c->variant);
+        ngx_memcpy(h.variant, c->variant, NGX_HTTP_CACHE_KEY_LEN);
+    }
+
     (void) ngx_write_file(&file, (u_char *) &h,
                           sizeof(ngx_http_file_cache_header_t), 0);
 
--- a/src/http/ngx_http_upstream.c	Mon Oct 27 21:13:39 2014 +0300
+++ b/src/http/ngx_http_upstream.c	Mon Oct 27 21:13:58 2014 +0300
@@ -4160,7 +4160,17 @@
         return NGX_OK;
     }
 
-    u->cacheable = 0;
+    if (r->cache == NULL) {
+        return NGX_OK;
+    }
+
+    if (h->value.len > NGX_HTTP_CACHE_VARY_LEN
+        || (h->value.len == 1 && h->value.data[0] == '*'))
+    {
+        u->cacheable = 0;
+    }
+
+    r->cache->vary = h->value;
 
 #endif