[PATCH 14 of 14] Cache: add HTTP/2 support

Piotr Sikora piotrsikora at google.com
Thu Jun 22 20:33:18 UTC 2017


# HG changeset patch
# User Piotr Sikora <piotrsikora at google.com>
# Date 1491954701 25200
#      Tue Apr 11 16:51:41 2017 -0700
# Node ID 432abcf285745ec6b6ee14d6487a4a42b51b7bce
# Parent  cde1f42da7b26b7d2b788f916685e736b919138e
Cache: add HTTP/2 support.

Signed-off-by: Piotr Sikora <piotrsikora at google.com>

diff -r cde1f42da7b2 -r 432abcf28574 src/http/modules/ngx_http_proxy_module.c
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -132,6 +132,14 @@ static ngx_int_t ngx_http_proxy_reinit_r
 static ngx_int_t ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in);
 static ngx_int_t ngx_http_proxy_process_status_line(ngx_http_request_t *r);
 static ngx_int_t ngx_http_proxy_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_process_trailer(ngx_http_request_t *r,
+    ngx_buf_t *buf);
+#if (NGX_HTTP_V2 && NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_proxy_serialize_headers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static ngx_chain_t *ngx_http_proxy_serialize_trailers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+#endif
 static ngx_int_t ngx_http_proxy_input_filter_init(void *data);
 static ngx_int_t ngx_http_proxy_copy_filter(ngx_event_pipe_t *p,
     ngx_buf_t *buf);
@@ -917,6 +925,7 @@ ngx_http_proxy_handler(ngx_http_request_
     u->create_request = ngx_http_proxy_create_request;
     u->reinit_request = ngx_http_proxy_reinit_request;
     u->process_header = ngx_http_proxy_process_status_line;
+    u->process_trailer = ngx_http_proxy_process_trailer;
     u->abort_request = ngx_http_proxy_abort_request;
     u->finalize_request = ngx_http_proxy_finalize_request;
     r->state = 0;
@@ -929,6 +938,14 @@ ngx_http_proxy_handler(ngx_http_request_
         u->create_request = ngx_http_proxy_create_v2_request;
         u->output.output_filter = ngx_http_v2_upstream_output_filter;
         u->output.filter_ctx = r;
+
+#if (NGX_HTTP_CACHE)
+
+        u->serialize_headers = ngx_http_proxy_serialize_headers;
+        u->serialize_trailers = ngx_http_proxy_serialize_trailers;
+
+#endif
+
     }
 
 #endif
@@ -2482,6 +2499,227 @@ ngx_http_proxy_process_header(ngx_http_r
 
 
 static ngx_int_t
+ngx_http_proxy_process_trailer(ngx_http_request_t *r, ngx_buf_t *buf)
+{
+    ngx_int_t         rc;
+    ngx_table_elt_t  *h;
+
+    for ( ;; ) {
+
+        rc = ngx_http_parse_header_line(r, buf, 1);
+
+        if (rc == NGX_OK) {
+            h = ngx_list_push(&r->upstream->headers_in.trailers);
+            if (h == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->hash = r->header_hash;
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->value.len = r->header_end - r->header_start;
+
+            h->key.data = ngx_pnalloc(r->pool,
+                               h->key.len + 1 + h->value.len + 1 + h->key.len);
+            if (h->key.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->value.data = h->key.data + h->key.len + 1;
+            h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+            ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+            h->key.data[h->key.len] = '\0';
+            ngx_memcpy(h->value.data, r->header_start, h->value.len);
+            h->value.data[h->value.len] = '\0';
+
+            if (h->key.len == r->lowcase_index) {
+                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+            } else {
+                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy trailer: \"%V: %V\"",
+                           &h->key, &h->value);
+
+            continue;
+        }
+
+        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy trailer done");
+
+            return NGX_OK;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent invalid trailer");
+
+        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+    }
+}
+
+
+#if (NGX_HTTP_V2 && NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_proxy_serialize_headers(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    size_t            len;
+    ngx_buf_t        *b;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    len = sizeof("HTTP/1.1 " CRLF) - 1 + u->headers_in.status_line.len
+          + sizeof(CRLF) - 1;
+
+    part = &u->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;
+        }
+
+        len += header[i].key.len + sizeof(": ") - 1
+               + header[i].value.len + sizeof(CRLF) - 1;
+    }
+
+    b = &u->buffer;
+
+    if (len > (size_t) (b->end - b->last)) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "upstream sent headers too big to serialize for cache, "
+                      "need:%uz available:%uz", len, b->end - b->last);
+
+        return NGX_ERROR;
+    }
+
+    b->last = ngx_copy(b->last, "HTTP/1.1 ", sizeof("HTTP/1.1 ") - 1);
+    b->last = ngx_copy(b->last, u->headers_in.status_line.data,
+                       u->headers_in.status_line.len);
+    *b->last++ = CR; *b->last++ = LF;
+
+    part = &u->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;
+        }
+
+        b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+        *b->last++ = ':'; *b->last++ = ' ';
+
+        b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    *b->last++ = CR; *b->last++ = LF;
+    b->pos = b->last;
+
+    return NGX_OK;
+}
+
+
+static ngx_chain_t *
+ngx_http_proxy_serialize_trailers(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+    size_t            len;
+    ngx_buf_t        *b;
+    ngx_uint_t        i;
+    ngx_chain_t      *cl;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    len = 0;
+
+    part = &u->headers_in.trailers.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;
+        }
+
+        len += header[i].key.len + sizeof(": ") - 1
+               + header[i].value.len + sizeof(CRLF) - 1;
+    }
+
+    if (len == 0) {
+        return NULL;
+    }
+
+    len += sizeof(CRLF) - 1;
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_CHAIN_ERROR;
+    }
+
+    *b->last++ = CR; *b->last++ = LF;
+
+    part = &u->headers_in.trailers.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;
+        }
+
+        b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+        *b->last++ = ':'; *b->last++ = ' ';
+
+        b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_CHAIN_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = NULL;
+
+    return cl;
+}
+
+#endif
+
+
+static ngx_int_t
 ngx_http_proxy_input_filter_init(void *data)
 {
     ngx_http_request_t    *r = data;
@@ -3995,18 +4233,6 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t
 #if (NGX_HTTP_CACHE)
 
     if (conf->upstream.cache) {
-
-#if (NGX_HTTP_V2)
-
-        if (conf->http_version == NGX_HTTP_VERSION_20) {
-            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                               "\"proxy_cache\" doesn't work with "
-                               "\"proxy_http_version 2.0\"");
-            return NGX_CONF_ERROR;
-        }
-
-#endif
-
         rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers_cache,
                                          ngx_http_proxy_cache_headers);
         if (rc != NGX_OK) {
@@ -4030,26 +4256,13 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t
 #endif
     }
 
-    if (conf->upstream.pass_trailers) {
-
-        if (conf->http_version != NGX_HTTP_VERSION_20) {
-            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                               "\"proxy_pass_trailers\" requires "
-                               "\"proxy_http_version 2.0\"");
-            return NGX_CONF_ERROR;
-        }
-
-#if (NGX_HTTP_CACHE)
-
-        if (conf->upstream.cache) {
-            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                               "\"proxy_pass_trailers\" doesn't work with "
-                               "\"proxy_cache\"");
-            return NGX_CONF_ERROR;
-        }
-
-#endif
-
+    if (conf->upstream.pass_trailers
+        && conf->http_version != NGX_HTTP_VERSION_20)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"proxy_pass_trailers\" requires "
+                           "\"proxy_http_version 2.0\"");
+        return NGX_CONF_ERROR;
     }
 
     return NGX_CONF_OK;
diff -r cde1f42da7b2 -r 432abcf28574 src/http/ngx_http_cache.h
--- a/src/http/ngx_http_cache.h
+++ b/src/http/ngx_http_cache.h
@@ -27,7 +27,7 @@
 #define NGX_HTTP_CACHE_ETAG_LEN      128
 #define NGX_HTTP_CACHE_VARY_LEN      128
 
-#define NGX_HTTP_CACHE_VERSION       5
+#define NGX_HTTP_CACHE_VERSION       6
 
 
 typedef struct {
@@ -82,6 +82,7 @@ struct ngx_http_cache_s {
 
     size_t                           header_start;
     size_t                           body_start;
+    off_t                            body_length;
     off_t                            length;
     off_t                            fs_size;
 
@@ -130,6 +131,7 @@ typedef struct {
     time_t                           error_sec;
     time_t                           last_modified;
     time_t                           date;
+    off_t                            body_length;
     uint32_t                         crc32;
     u_short                          valid_msec;
     u_short                          header_start;
@@ -192,6 +194,8 @@ ngx_int_t ngx_http_file_cache_set_header
 void ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf);
 void ngx_http_file_cache_update_header(ngx_http_request_t *r);
 ngx_int_t ngx_http_cache_send(ngx_http_request_t *);
+ngx_buf_t *ngx_http_cache_get_trailers(ngx_http_request_t *r,
+    ngx_http_cache_t *c);
 void ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf);
 time_t ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status);
 
diff -r cde1f42da7b2 -r 432abcf28574 src/http/ngx_http_file_cache.c
--- a/src/http/ngx_http_file_cache.c
+++ b/src/http/ngx_http_file_cache.c
@@ -605,6 +605,7 @@ ngx_http_file_cache_read(ngx_http_reques
     c->error_sec = h->error_sec;
     c->last_modified = h->last_modified;
     c->date = h->date;
+    c->body_length = h->body_length;
     c->valid_msec = h->valid_msec;
     c->body_start = h->body_start;
     c->etag.len = h->etag_len;
@@ -1260,6 +1261,7 @@ ngx_http_file_cache_set_header(ngx_http_
     h->error_sec = c->error_sec;
     h->last_modified = c->last_modified;
     h->date = c->date;
+    h->body_length = c->body_length;
     h->crc32 = c->crc32;
     h->valid_msec = (u_short) c->valid_msec;
     h->header_start = (u_short) c->header_start;
@@ -1500,6 +1502,7 @@ ngx_http_file_cache_update_header(ngx_ht
 
     if (h.version != NGX_HTTP_CACHE_VERSION
         || h.last_modified != c->last_modified
+        || (h.body_length != -1 && h.body_length != c->body_length)
         || h.crc32 != c->crc32
         || (size_t) h.header_start != c->header_start
         || (size_t) h.body_start != c->body_start)
@@ -1523,6 +1526,7 @@ ngx_http_file_cache_update_header(ngx_ht
     h.error_sec = c->error_sec;
     h.last_modified = c->last_modified;
     h.date = c->date;
+    h.body_length = c->body_length;
     h.crc32 = c->crc32;
     h.valid_msec = (u_short) c->valid_msec;
     h.header_start = (u_short) c->header_start;
@@ -1561,6 +1565,7 @@ done:
 ngx_int_t
 ngx_http_cache_send(ngx_http_request_t *r)
 {
+    off_t              body_len;
     ngx_int_t          rc;
     ngx_buf_t         *b;
     ngx_chain_t        out;
@@ -1571,7 +1576,14 @@ ngx_http_cache_send(ngx_http_request_t *
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "http file cache send: %s", c->file.name.data);
 
-    if (r != r->main && c->length - c->body_start == 0) {
+    if (c->body_length != -1) {
+        body_len = c->body_length;
+
+    } else {
+        body_len = c->length - c->body_start;
+    }
+
+    if (r != r->main && body_len == 0) {
         return ngx_http_send_header(r);
     }
 
@@ -1594,9 +1606,9 @@ ngx_http_cache_send(ngx_http_request_t *
     }
 
     b->file_pos = c->body_start;
-    b->file_last = c->length;
-
-    b->in_file = (c->length - c->body_start) ? 1: 0;
+    b->file_last = c->body_start + body_len;
+
+    b->in_file = body_len ? 1 : 0;
     b->last_buf = (r == r->main) ? 1: 0;
     b->last_in_chain = 1;
 
@@ -1611,6 +1623,42 @@ ngx_http_cache_send(ngx_http_request_t *
 }
 
 
+ngx_buf_t *
+ngx_http_cache_get_trailers(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+    off_t       offset;
+    size_t      len;
+    ssize_t     n;
+    ngx_buf_t  *b;
+
+    offset = c->body_start + c->body_length + sizeof(CRLF) - 1;
+    len = c->length - offset;
+
+    b = ngx_create_temp_buf(r->pool, len + sizeof(CRLF) - 1);
+    if (b == NULL) {
+        return NULL;
+    }
+
+    n = ngx_read_file(&c->file, b->pos, len, offset);
+
+    if (n == NGX_ERROR) {
+        return NULL;
+    }
+
+    if ((size_t) n != len) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      ngx_read_file_n " read only %z of %z from \"%s\"",
+                      n, len, c->file.name.data);
+        return NULL;
+    }
+
+    b->last += n;
+    *b->last++ = CR; *b->last++ = LF;
+
+    return b;
+}
+
+
 void
 ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf)
 {
diff -r cde1f42da7b2 -r 432abcf28574 src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -58,6 +58,10 @@ static ngx_int_t ngx_http_upstream_proce
     ngx_http_upstream_t *u);
 static ngx_int_t ngx_http_upstream_process_trailers(ngx_http_request_t *r,
     ngx_http_upstream_t *u);
+#if (NGX_HTTP_V2 && NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_upstream_serialize_trailers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+#endif
 static void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r,
     ngx_http_upstream_t *u);
 static void ngx_http_upstream_send_response(ngx_http_request_t *r,
@@ -1035,6 +1039,7 @@ static ngx_int_t
 ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u)
 {
     ngx_int_t          rc;
+    ngx_buf_t         *trailers;
     ngx_http_cache_t  *c;
 
     r->cached = 1;
@@ -1076,6 +1081,24 @@ ngx_http_upstream_cache_send(ngx_http_re
             return NGX_DONE;
         }
 
+        if (c->body_length != -1 && u->conf->pass_trailers) {
+
+            trailers = ngx_http_cache_get_trailers(r, c);
+            if (trailers == NULL) {
+                return NGX_ERROR;
+            }
+
+            if (u->process_trailer(r, trailers) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            r->expect_trailers = 1;
+
+            if (ngx_http_upstream_process_trailers(r, u) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
         return ngx_http_cache_send(r);
     }
 
@@ -2955,6 +2978,49 @@ ngx_http_upstream_process_trailers(ngx_h
 }
 
 
+#if (NGX_HTTP_V2 && NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_upstream_serialize_trailers(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    off_t         body_len;
+    ngx_chain_t  *cl;
+
+    if (u->serialize_trailers == NULL) {
+        return NGX_OK;
+    }
+
+    cl = u->serialize_trailers(r, u);
+
+    if (cl == NGX_CHAIN_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (cl == NULL) {
+        return NGX_OK;
+    }
+
+    body_len = u->pipe->temp_file->offset - (off_t) r->cache->body_start;
+
+    if (ngx_write_chain_to_temp_file(u->pipe->temp_file, cl) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_write_file(&u->pipe->temp_file->file,
+                       (u_char *) &body_len, sizeof(off_t),
+                       offsetof(ngx_http_file_cache_header_t, body_length))
+        != sizeof(off_t))
+    {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
 static void
 ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r,
     ngx_http_upstream_t *u)
@@ -3201,9 +3267,16 @@ ngx_http_upstream_send_response(ngx_http
             }
         }
 
+        if (valid && u->serialize_headers) {
+            if (u->serialize_headers(r, u) != NGX_OK) {
+                valid = 0;
+            }
+        }
+
         if (valid) {
             r->cache->date = now;
             r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start);
+            r->cache->body_length = -1;
 
             if (u->headers_in.status_n == NGX_HTTP_OK
                 || u->headers_in.status_n == NGX_HTTP_PARTIAL_CONTENT)
@@ -4107,6 +4180,13 @@ ngx_http_upstream_process_request(ngx_ht
         if (u->cacheable) {
 
             if (p->upstream_done) {
+
+                if (ngx_http_upstream_serialize_trailers(r, u) != NGX_OK) {
+                    ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    return;
+                }
+
                 ngx_http_file_cache_update(r, p->temp_file);
 
             } else if (p->upstream_eof) {
@@ -4118,6 +4198,12 @@ ngx_http_upstream_process_request(ngx_ht
                         || u->headers_in.content_length_n
                            == tf->offset - (off_t) r->cache->body_start))
                 {
+                    if (ngx_http_upstream_serialize_trailers(r, u) != NGX_OK) {
+                        ngx_http_upstream_finalize_request(r, u,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                        return;
+                    }
+
                     ngx_http_file_cache_update(r, tf);
 
                 } else {
diff -r cde1f42da7b2 -r 432abcf28574 src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -359,6 +359,12 @@ struct ngx_http_upstream_s {
     ngx_int_t                      (*create_request)(ngx_http_request_t *r);
     ngx_int_t                      (*reinit_request)(ngx_http_request_t *r);
     ngx_int_t                      (*process_header)(ngx_http_request_t *r);
+    ngx_int_t                      (*process_trailer)(ngx_http_request_t *r,
+                                         ngx_buf_t *buf);
+    ngx_int_t                      (*serialize_headers)(ngx_http_request_t *r,
+                                         ngx_http_upstream_t *u);
+    ngx_chain_t                   *(*serialize_trailers)(ngx_http_request_t *r,
+                                         ngx_http_upstream_t *u);
     void                           (*abort_request)(ngx_http_request_t *r);
     void                           (*finalize_request)(ngx_http_request_t *r,
                                          ngx_int_t rc);


More information about the nginx-devel mailing list