[PATCH 2 of 3] HTTP/2: added support for trailers in HTTP responses

Piotr Sikora piotrsikora at google.com
Tue Jun 13 12:19:54 UTC 2017


# HG changeset patch
# User Piotr Sikora <piotrsikora at google.com>
# Date 1490351854 25200
#      Fri Mar 24 03:37:34 2017 -0700
# Node ID 73f67e06ab103e0368d1810c6f8cac5c70c4e246
# Parent  07a5d26b49f04425ff54cc998f885aa987b7823f
HTTP/2: added support for trailers in HTTP responses.

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

diff -r 07a5d26b49f0 -r 73f67e06ab10 src/http/v2/ngx_http_v2_filter_module.c
--- a/src/http/v2/ngx_http_v2_filter_module.c
+++ b/src/http/v2/ngx_http_v2_filter_module.c
@@ -50,13 +50,17 @@
 #define NGX_HTTP_V2_SERVER_INDEX          54
 #define NGX_HTTP_V2_VARY_INDEX            59
 
+#define NGX_HTTP_V2_NO_TRAILERS           (ngx_http_v2_out_frame_t *) -1
+
 
 static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
     u_char *tmp, ngx_uint_t lower);
 static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
     ngx_uint_t value);
 static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
-    ngx_http_request_t *r, u_char *pos, u_char *end);
+    ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin);
+static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame(
+    ngx_http_request_t *r);
 
 static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc,
     ngx_chain_t *in, off_t limit);
@@ -612,7 +616,7 @@ ngx_http_v2_header_filter(ngx_http_reque
                                       header[i].value.len, tmp);
     }
 
-    frame = ngx_http_v2_create_headers_frame(r, start, pos);
+    frame = ngx_http_v2_create_headers_frame(r, start, pos, r->header_only);
     if (frame == NULL) {
         return NGX_ERROR;
     }
@@ -636,6 +640,118 @@ ngx_http_v2_header_filter(ngx_http_reque
 }
 
 
+static ngx_http_v2_out_frame_t *
+ngx_http_v2_create_trailers_frame(ngx_http_request_t *r)
+{
+    u_char           *pos, *start, *tmp;
+    size_t            len, tmp_len;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    len = 0;
+    tmp_len = 0;
+
+    part = &r->headers_out.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;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                          "too long response trailer name: \"%V\"",
+                          &header[i].key);
+            return NULL;
+        }
+
+        if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                          "too long response trailer value: \"%V: %V\"",
+                          &header[i].key, &header[i].value);
+            return NULL;
+        }
+
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
+                 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
+
+        if (header[i].key.len > tmp_len) {
+            tmp_len = header[i].key.len;
+        }
+
+        if (header[i].value.len > tmp_len) {
+            tmp_len = header[i].value.len;
+        }
+    }
+
+    if (len == 0) {
+        return NGX_HTTP_V2_NO_TRAILERS;
+    }
+
+    tmp = ngx_palloc(r->pool, tmp_len);
+    pos = ngx_pnalloc(r->pool, len);
+
+    if (pos == NULL || tmp == NULL) {
+        return NULL;
+    }
+
+    start = pos;
+
+    part = &r->headers_out.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;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+#if (NGX_DEBUG)
+        if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) {
+            ngx_strlow(tmp, header[i].key.data, header[i].key.len);
+
+            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http2 output trailer: \"%*s: %V\"",
+                           header[i].key.len, tmp, &header[i].value);
+        }
+#endif
+
+        *pos++ = 0;
+
+        pos = ngx_http_v2_write_name(pos, header[i].key.data,
+                                     header[i].key.len, tmp);
+
+        pos = ngx_http_v2_write_value(pos, header[i].value.data,
+                                      header[i].value.len, tmp);
+    }
+
+    return ngx_http_v2_create_headers_frame(r, start, pos, 1);
+}
+
+
 static u_char *
 ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
     ngx_uint_t lower)
@@ -686,7 +802,7 @@ ngx_http_v2_write_int(u_char *pos, ngx_u
 
 static ngx_http_v2_out_frame_t *
 ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
-    u_char *end)
+    u_char *end, ngx_uint_t fin)
 {
     u_char                    type, flags;
     size_t                    rest, frame_size;
@@ -707,12 +823,12 @@ ngx_http_v2_create_headers_frame(ngx_htt
     frame->stream = stream;
     frame->length = rest;
     frame->blocked = 1;
-    frame->fin = r->header_only;
+    frame->fin = fin;
 
     ll = &frame->first;
 
     type = NGX_HTTP_V2_HEADERS_FRAME;
-    flags = r->header_only ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG;
+    flags = fin ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG;
     frame_size = stream->connection->frame_size;
 
     for ( ;; ) {
@@ -776,7 +892,7 @@ ngx_http_v2_create_headers_frame(ngx_htt
             continue;
         }
 
-        b->last_buf = r->header_only;
+        b->last_buf = fin;
         cl->next = NULL;
         frame->last = cl;
 
@@ -798,7 +914,7 @@ ngx_http_v2_send_chain(ngx_connection_t 
     ngx_http_request_t        *r;
     ngx_http_v2_stream_t      *stream;
     ngx_http_v2_loc_conf_t    *h2lcf;
-    ngx_http_v2_out_frame_t   *frame;
+    ngx_http_v2_out_frame_t   *frame, *trailers;
     ngx_http_v2_connection_t  *h2c;
 
     r = fc->data;
@@ -872,6 +988,8 @@ ngx_http_v2_send_chain(ngx_connection_t 
     frame_size = (h2lcf->chunk_size < h2c->frame_size)
                  ? h2lcf->chunk_size : h2c->frame_size;
 
+    trailers = NGX_HTTP_V2_NO_TRAILERS;
+
 #if (NGX_SUPPRESS_WARN)
     cl = NULL;
 #endif
@@ -934,19 +1052,39 @@ ngx_http_v2_send_chain(ngx_connection_t 
             size -= rest;
         }
 
-        frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl);
-        if (frame == NULL) {
-            return NGX_CHAIN_ERROR;
+        if (cl->buf->last_buf) {
+            trailers = ngx_http_v2_create_trailers_frame(r);
+            if (trailers == NULL) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
+                cl->buf->last_buf = 0;
+            }
         }
 
-        ngx_http_v2_queue_frame(h2c, frame);
+        if (frame_size || cl->buf->last_buf) {
+            frame = ngx_http_v2_filter_get_data_frame(stream, frame_size,
+                                                      out, cl);
+            if (frame == NULL) {
+                return NGX_CHAIN_ERROR;
+            }
 
-        h2c->send_window -= frame_size;
+            ngx_http_v2_queue_frame(h2c, frame);
 
-        stream->send_window -= frame_size;
-        stream->queued++;
+            h2c->send_window -= frame_size;
+
+            stream->send_window -= frame_size;
+            stream->queued++;
+        }
 
         if (in == NULL) {
+
+            if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
+                ngx_http_v2_queue_frame(h2c, trailers);
+                stream->queued++;
+            }
+
             break;
         }
 


More information about the nginx-devel mailing list