]> git.kaiwu.me - nginx.git/commitdiff
Cache: hash of Vary headers now stored in cache.
authorMaxim Dounin <mdounin@mdounin.ru>
Mon, 27 Oct 2014 18:13:58 +0000 (21:13 +0300)
committerMaxim Dounin <mdounin@mdounin.ru>
Mon, 27 Oct 2014 18:13:58 +0000 (21:13 +0300)
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.

src/http/ngx_http_cache.h
src/http/ngx_http_file_cache.c
src/http/ngx_http_upstream.c

index 1cfd9fe845fad2166cff8462a7dc94c7e638b0e9..a5886631b38c9ed80affa4be3d3cf87f9b88507b 100644 (file)
@@ -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 @@ struct ngx_http_cache_s {
     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 @@ typedef struct {
     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;
 
 
index 2eebc3068ef9a8a15cf3c72c65ef317f7037b780..e1b16e9c6f26be8c87a7f9c50c92fcb2fee11dbb 100644 (file)
@@ -29,6 +29,10 @@ static ngx_http_file_cache_node_t *
     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 @@ ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c)
         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 @@ ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
 }
 
 
+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_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
         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_http_file_cache_update_header(ngx_http_request_t *r)
         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);
 
index 9a70b8c7ea3467b5a64a1df413b5bfa5fda25aad..df7e5f4ea4e33933dacca6852a89091f66d949cd 100644 (file)
@@ -4160,7 +4160,17 @@ ngx_http_upstream_process_vary(ngx_http_request_t *r,
         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