Merge upstream version of HTTP trailers.

Commits upstream:
- http://hg.nginx.org/nginx/rev/1b068a4e82d8
- http://hg.nginx.org/nginx/rev/4e784e095a97
- http://hg.nginx.org/nginx/rev/8666da1ecf33

Notable difference between this and previously committed patches
is lack of the requirement for "TE: trailers" header in requests.

Change-Id: Ib0a2a4b53966bc012373442d933c960e27f3d7fb
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Reviewed-on: https://nginx-review.googlesource.com/3040
Reviewed-by: Lizan Zhou <zlizan@google.com>
diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c
index ffa1dd2..4d6fd3e 100644
--- a/src/http/modules/ngx_http_chunked_filter_module.c
+++ b/src/http/modules/ngx_http_chunked_filter_module.c
@@ -17,7 +17,8 @@
 
 
 static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
-static ngx_chain_t *ngx_http_chunked_get_trailers(ngx_http_request_t *r);
+static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r,
+    ngx_http_chunked_filter_ctx_t *ctx);
 
 
 static ngx_http_module_t  ngx_http_chunked_filter_module_ctx = {
@@ -70,35 +71,32 @@
         return ngx_http_next_header_filter(r);
     }
 
-    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-
-    if (clcf->chunked_transfer_encoding
-        && r->allow_trailers && r->expect_trailers)
+    if (r->headers_out.content_length_n == -1
+        || r->expect_trailers)
     {
-        ngx_http_clear_content_length(r);
-        r->chunked = 1;
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 
-    } else if (r->headers_out.content_length_n == -1) {
-        if (r->http_version < NGX_HTTP_VERSION_11) {
-            r->keepalive = 0;
+        if (r->http_version >= NGX_HTTP_VERSION_11
+            && clcf->chunked_transfer_encoding)
+        {
+            if (r->expect_trailers) {
+                ngx_http_clear_content_length(r);
+            }
 
-        } else if (clcf->chunked_transfer_encoding) {
             r->chunked = 1;
 
-        } else {
+            ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_filter_ctx_t));
+            if (ctx == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module);
+
+        } else if (r->headers_out.content_length_n == -1) {
             r->keepalive = 0;
         }
     }
 
-    if (r->chunked) {
-        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_filter_ctx_t));
-        if (ctx == NULL) {
-            return NGX_ERROR;
-        }
-
-        ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module);
-    }
-
     return ngx_http_next_header_filter(r);
 }
 
@@ -185,35 +183,17 @@
     }
 
     if (cl->buf->last_buf) {
-        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+        tl = ngx_http_chunked_create_trailers(r, ctx);
         if (tl == NULL) {
             return NGX_ERROR;
         }
 
-        b = tl->buf;
-
-        b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
-        b->temporary = 0;
-        b->memory = 1;
-        b->last_buf = 1;
-        b->pos = (u_char *) CRLF "0" CRLF CRLF;
-        b->last = b->pos + 7;
-
         cl->buf->last_buf = 0;
 
         *ll = tl;
 
         if (size == 0) {
-            b->pos += 2;
-        }
-
-        if (r->allow_trailers && r->expect_trailers) {
-            tl->next = ngx_http_chunked_get_trailers(r);
-
-            if (tl->next != NULL) {
-                b->last -= 2;
-                b->last_buf = 0;
-            }
+            tl->buf->pos += 2;
         }
 
     } else if (size > 0) {
@@ -246,19 +226,15 @@
 
 
 static ngx_chain_t *
-ngx_http_chunked_get_trailers(ngx_http_request_t *r)
+ngx_http_chunked_create_trailers(ngx_http_request_t *r,
+    ngx_http_chunked_filter_ctx_t *ctx)
 {
-    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;
-    ngx_http_chunked_filter_ctx_t  *ctx;
-
-    if (ngx_http_eval_trailers(r) != NGX_OK) {
-        return NULL;
-    }
+    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;
 
@@ -281,18 +257,10 @@
             continue;
         }
 
-        len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
-               + sizeof(CRLF) - 1;
+        len += header[i].key.len + sizeof(": ") - 1
+               + header[i].value.len + sizeof(CRLF) - 1;
     }
 
-    if (len == 0) {
-        return NULL;
-    }
-
-    len += sizeof(CRLF) - 1;
-
-    ctx = ngx_http_get_module_ctx(r, ngx_http_chunked_filter_module);
-
     cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
     if (cl == NULL) {
         return NULL;
@@ -305,14 +273,24 @@
     b->memory = 1;
     b->last_buf = 1;
 
-    b->start = ngx_palloc(r->pool, len);
-    if (b->start == NULL) {
+    if (len == 0) {
+        b->pos = (u_char *) CRLF "0" CRLF CRLF;
+        b->last = b->pos + sizeof(CRLF "0" CRLF CRLF) - 1;
+        return cl;
+    }
+
+    len += sizeof(CRLF "0" CRLF CRLF) - 1;
+
+    b->pos = ngx_palloc(r->pool, len);
+    if (b->pos == NULL) {
         return NULL;
     }
 
-    b->end = b->last + len;
-    b->pos = b->start;
-    b->last = b->start;
+    b->last = b->pos;
+
+    *b->last++ = CR; *b->last++ = LF;
+    *b->last++ = '0';
+    *b->last++ = CR; *b->last++ = LF;
 
     part = &r->headers_out.trailers.part;
     header = part->elts;
@@ -344,7 +322,6 @@
         *b->last++ = CR; *b->last++ = LF;
     }
 
-    /* the end of HTTP trailer */
     *b->last++ = CR; *b->last++ = LF;
 
     return cl;
diff --git a/src/http/modules/ngx_http_headers_filter_module.c b/src/http/modules/ngx_http_headers_filter_module.c
index 47ce779..29e2787 100644
--- a/src/http/modules/ngx_http_headers_filter_module.c
+++ b/src/http/modules/ngx_http_headers_filter_module.c
@@ -73,8 +73,6 @@
     void *conf);
 static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
-static char *ngx_http_headers_add_trailer(ngx_conf_t *cf, ngx_command_t *cmd,
-    void *conf);
 
 
 static ngx_http_set_header_t  ngx_http_set_headers[] = {
@@ -108,16 +106,16 @@
                         |NGX_CONF_TAKE23,
       ngx_http_headers_add,
       NGX_HTTP_LOC_CONF_OFFSET,
-      0,
-      NULL},
+      offsetof(ngx_http_headers_conf_t, headers),
+      NULL },
 
     { ngx_string("add_trailer"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                         |NGX_CONF_TAKE23,
-      ngx_http_headers_add_trailer,
+      ngx_http_headers_add,
       NGX_HTTP_LOC_CONF_OFFSET,
-      0,
-      NULL},
+      offsetof(ngx_http_headers_conf_t, trailers),
+      NULL },
 
       ngx_null_command
 };
@@ -155,19 +153,16 @@
 
 
 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
 
 
 static ngx_int_t
 ngx_http_headers_filter(ngx_http_request_t *r)
 {
-    u_char                    *p, *data;
-    size_t                     len;
-    ngx_str_t                  value;
-    ngx_uint_t                 i, safe_status;
-    ngx_table_elt_t           *t;
-    ngx_http_header_val_t     *h;
-    ngx_http_headers_conf_t   *conf;
-    ngx_http_core_loc_conf_t  *clcf;
+    ngx_str_t                 value;
+    ngx_uint_t                i, safe_status;
+    ngx_http_header_val_t    *h;
+    ngx_http_headers_conf_t  *conf;
 
     if (r != r->main) {
         return ngx_http_next_header_filter(r);
@@ -176,8 +171,8 @@
     conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
 
     if (conf->expires == NGX_HTTP_EXPIRES_OFF
-         && conf->headers == NULL
-         && conf->trailers == NULL)
+        && conf->headers == NULL
+        && conf->trailers == NULL)
     {
         return ngx_http_next_header_filter(r);
     }
@@ -226,27 +221,7 @@
         }
     }
 
-    if (conf->trailers && r->allow_trailers) {
-
-        if (r->http_version < NGX_HTTP_VERSION_20) {
-            if (r->header_only
-                || r->headers_out.status == NGX_HTTP_NOT_MODIFIED
-                || r->headers_out.status == NGX_HTTP_NO_CONTENT
-                || r->headers_out.status < NGX_HTTP_OK
-                || r->method == NGX_HTTP_HEAD)
-            {
-               return ngx_http_next_header_filter(r);
-            }
-
-            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-
-            if (!clcf->chunked_transfer_encoding) {
-                return ngx_http_next_header_filter(r);
-            }
-        }
-
-        len = 0;
-
+    if (conf->trailers) {
         h = conf->trailers->elts;
         for (i = 0; i < conf->trailers->nelts; i++) {
 
@@ -254,54 +229,9 @@
                 continue;
             }
 
-            if (h[i].value.value.len) {
-                len += h[i].key.len + sizeof(", ") - 1;
-            }
+            r->expect_trailers = 1;
+            break;
         }
-
-        if (len == 0) {
-            return ngx_http_next_header_filter(r);
-        }
-
-        len -= sizeof(", ") - 1;
-
-        t = ngx_list_push(&r->headers_out.headers);
-        if (t == NULL) {
-            return NGX_ERROR;
-        }
-
-        data = ngx_pnalloc(r->pool, len);
-        if (data == NULL) {
-            return NGX_ERROR;
-        }
-
-        p = data;
-
-        h = conf->trailers->elts;
-        for (i = 0; i < conf->trailers->nelts; i++) {
-
-            if (!safe_status && !h[i].always) {
-                continue;
-            }
-
-            if (h[i].value.value.len) {
-                p = ngx_copy(p, h[i].key.data, h[i].key.len);
-
-                if (p == data + len) {
-                    break;
-                }
-
-                *p++ = ','; *p++ = ' ';
-            }
-        }
-
-        ngx_str_set(&t->key, "Trailer");
-        t->value.data = data;
-        t->value.len = len;
-        t->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash(
-                           ngx_hash('t', 'r'), 'a'), 'i'), 'l'), 'e'), 'r');
-
-        r->expect_trailers = 1;
     }
 
     return ngx_http_next_header_filter(r);
@@ -309,6 +239,83 @@
 
 
 static ngx_int_t
+ngx_http_trailers_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_str_t                 value;
+    ngx_uint_t                i, safe_status;
+    ngx_chain_t              *cl;
+    ngx_table_elt_t          *t;
+    ngx_http_header_val_t    *h;
+    ngx_http_headers_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
+
+    if (in == NULL
+        || conf->trailers == NULL
+        || !r->expect_trailers
+        || r->header_only)
+    {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    for (cl = in; cl; cl = cl->next) {
+        if (cl->buf->last_buf) {
+            break;
+        }
+    }
+
+    if (cl == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    switch (r->headers_out.status) {
+
+    case NGX_HTTP_OK:
+    case NGX_HTTP_CREATED:
+    case NGX_HTTP_NO_CONTENT:
+    case NGX_HTTP_PARTIAL_CONTENT:
+    case NGX_HTTP_MOVED_PERMANENTLY:
+    case NGX_HTTP_MOVED_TEMPORARILY:
+    case NGX_HTTP_SEE_OTHER:
+    case NGX_HTTP_NOT_MODIFIED:
+    case NGX_HTTP_TEMPORARY_REDIRECT:
+    case NGX_HTTP_PERMANENT_REDIRECT:
+        safe_status = 1;
+        break;
+
+    default:
+        safe_status = 0;
+        break;
+    }
+
+    h = conf->trailers->elts;
+    for (i = 0; i < conf->trailers->nelts; i++) {
+
+        if (!safe_status && !h[i].always) {
+            continue;
+        }
+
+        if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (value.len) {
+            t = ngx_list_push(&r->headers_out.trailers);
+            if (t == NULL) {
+                return NGX_ERROR;
+            }
+
+            t->key = h[i].key;
+            t->value = value;
+            t->hash = 1;
+        }
+    }
+
+    return ngx_http_next_body_filter(r, in);
+}
+
+
+static ngx_int_t
 ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf)
 {
     char                *err;
@@ -641,67 +648,6 @@
 }
 
 
-ngx_int_t
-ngx_http_eval_trailers(ngx_http_request_t *r)
-{
-    ngx_str_t                 value;
-    ngx_uint_t                i, safe_status;
-    ngx_table_elt_t          *t;
-    ngx_http_header_val_t    *h;
-    ngx_http_headers_conf_t  *conf;
-
-    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
-
-    if (conf->trailers == NULL) {
-        return NGX_OK;
-    }
-
-    switch (r->headers_out.status) {
-
-    case NGX_HTTP_OK:
-    case NGX_HTTP_CREATED:
-    case NGX_HTTP_NO_CONTENT:
-    case NGX_HTTP_PARTIAL_CONTENT:
-    case NGX_HTTP_MOVED_PERMANENTLY:
-    case NGX_HTTP_MOVED_TEMPORARILY:
-    case NGX_HTTP_SEE_OTHER:
-    case NGX_HTTP_NOT_MODIFIED:
-    case NGX_HTTP_TEMPORARY_REDIRECT:
-        safe_status = 1;
-        break;
-
-    default:
-        safe_status = 0;
-        break;
-    }
-
-    h = conf->trailers->elts;
-    for (i = 0; i < conf->trailers->nelts; i++) {
-
-        if (!safe_status && !h[i].always) {
-            continue;
-        }
-
-        if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
-            return NGX_ERROR;
-        }
-
-        if (value.len) {
-            t = ngx_list_push(&r->headers_out.trailers);
-            if (t == NULL) {
-                return NGX_ERROR;
-            }
-
-            t->key = h[i].key;
-            t->value = value;
-            t->hash = 1;
-        }
-    }
-
-    return NGX_OK;
-}
-
-
 static void *
 ngx_http_headers_create_conf(ngx_conf_t *cf)
 {
@@ -761,6 +707,9 @@
     ngx_http_next_header_filter = ngx_http_top_header_filter;
     ngx_http_top_header_filter = ngx_http_headers_filter;
 
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_trailers_filter;
+
     return NGX_OK;
 }
 
@@ -838,95 +787,26 @@
 {
     ngx_http_headers_conf_t *hcf = conf;
 
-    ngx_str_t                         *value;
-    ngx_uint_t                         i;
-    ngx_http_header_val_t             *hv;
-    ngx_http_set_header_t             *set;
-    ngx_http_compile_complex_value_t   ccv;
+    ngx_str_t                          *value;
+    ngx_uint_t                          i;
+    ngx_array_t                       **headers;
+    ngx_http_header_val_t              *hv;
+    ngx_http_set_header_t              *set;
+    ngx_http_compile_complex_value_t    ccv;
 
     value = cf->args->elts;
 
-    if (hcf->headers == NULL) {
-        hcf->headers = ngx_array_create(cf->pool, 1,
-                                        sizeof(ngx_http_header_val_t));
-        if (hcf->headers == NULL) {
+    headers = (ngx_array_t **) ((char *) hcf + cmd->offset);
+
+    if (*headers == NULL) {
+        *headers = ngx_array_create(cf->pool, 1,
+                                    sizeof(ngx_http_header_val_t));
+        if (*headers == NULL) {
             return NGX_CONF_ERROR;
         }
     }
 
-    hv = ngx_array_push(hcf->headers);
-    if (hv == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    hv->key = value[1];
-    hv->handler = ngx_http_add_header;
-    hv->offset = 0;
-    hv->always = 0;
-
-    set = ngx_http_set_headers;
-    for (i = 0; set[i].name.len; i++) {
-        if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) {
-            continue;
-        }
-
-        hv->offset = set[i].offset;
-        hv->handler = set[i].handler;
-
-        break;
-    }
-
-    if (value[2].len == 0) {
-        ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
-
-    } else {
-        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
-
-        ccv.cf = cf;
-        ccv.value = &value[2];
-        ccv.complex_value = &hv->value;
-
-        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
-            return NGX_CONF_ERROR;
-        }
-    }
-
-    if (cf->args->nelts == 3) {
-        return NGX_CONF_OK;
-    }
-
-    if (ngx_strcmp(value[3].data, "always") != 0) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "invalid parameter \"%V\"", &value[3]);
-        return NGX_CONF_ERROR;
-    }
-
-    hv->always = 1;
-
-    return NGX_CONF_OK;
-}
-
-
-static char *
-ngx_http_headers_add_trailer(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
-{
-    ngx_http_headers_conf_t *hcf = conf;
-
-    ngx_str_t                         *value;
-    ngx_http_header_val_t             *hv;
-    ngx_http_compile_complex_value_t   ccv;
-
-    value = cf->args->elts;
-
-    if (hcf->trailers == NULL) {
-        hcf->trailers = ngx_array_create(cf->pool, 1,
-                                         sizeof(ngx_http_header_val_t));
-        if (hcf->trailers == NULL) {
-            return NGX_CONF_ERROR;
-        }
-    }
-
-    hv = ngx_array_push(hcf->trailers);
+    hv = ngx_array_push(*headers);
     if (hv == NULL) {
         return NGX_CONF_ERROR;
     }
@@ -936,6 +816,22 @@
     hv->offset = 0;
     hv->always = 0;
 
+    if (headers == &hcf->headers) {
+        hv->handler = ngx_http_add_header;
+
+        set = ngx_http_set_headers;
+        for (i = 0; set[i].name.len; i++) {
+            if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) {
+                continue;
+            }
+
+            hv->offset = set[i].offset;
+            hv->handler = set[i].handler;
+
+            break;
+        }
+    }
+
     if (value[2].len == 0) {
         ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
 
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
index e5306ef..29879f0 100644
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -145,7 +145,6 @@
 ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r,
     ngx_module_t *m, ngx_int_t error);
 void ngx_http_clean_header(ngx_http_request_t *r);
-ngx_int_t ngx_http_eval_trailers(ngx_http_request_t *r);
 
 
 ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r);
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index 0dd7427..f2e7623 100644
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -29,8 +29,6 @@
     ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
-static ngx_int_t ngx_http_process_te(ngx_http_request_t *r,
-    ngx_table_elt_t *h, ngx_uint_t offset);
 
 static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
     ngx_uint_t alloc);
@@ -130,10 +128,6 @@
                  offsetof(ngx_http_headers_in_t, if_range),
                  ngx_http_process_unique_header_line },
 
-    { ngx_string("TE"),
-                 offsetof(ngx_http_headers_in_t, te),
-                 ngx_http_process_te },
-
     { ngx_string("Transfer-Encoding"),
                  offsetof(ngx_http_headers_in_t, transfer_encoding),
                  ngx_http_process_header_line },
@@ -1698,7 +1692,7 @@
 
 #if (NGX_HTTP_V2)
 
-    if (r->http_version >= NGX_HTTP_VERSION_20) {
+    if (r->stream) {
         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                       "client sent HTTP/2 request with \"Connection\" header");
 
@@ -1793,63 +1787,6 @@
 
 
 static ngx_int_t
-ngx_http_process_te(ngx_http_request_t *r, ngx_table_elt_t *h,
-    ngx_uint_t offset)
-{
-    u_char  *p;
-
-    if (ngx_http_process_multi_header_lines(r, h, offset) != NGX_OK) {
-        return NGX_ERROR;
-    }
-
-    if (r->http_version < NGX_HTTP_VERSION_11) {
-        return NGX_OK;
-    }
-
-    if (h->value.len == sizeof("trailers") - 1
-        && ngx_memcmp(h->value.data, "trailers", sizeof("trailers") - 1) == 0)
-    {
-        r->allow_trailers = 1;
-        return NGX_OK;
-    }
-
-#if (NGX_HTTP_V2)
-
-    if (r->http_version >= NGX_HTTP_VERSION_20) {
-        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
-                      "client sent HTTP/2 request with invalid value \"%V\" "
-                      "in \"TE\" header", &h->value);
-
-        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
-        return NGX_ERROR;
-    }
-
-#endif
-
-    if (h->value.len < sizeof("trailers") - 1) {
-        return NGX_OK;
-    }
-
-    p = ngx_strcasestrn(h->value.data, "trailers", sizeof("trailers") - 2);
-    if (p == NULL) {
-        return NGX_OK;
-    }
-
-    if (p == h->value.data || *(p - 1) == ',' || *(p - 1) == ' ') {
-
-        p += sizeof("trailers") - 1;
-
-        if (p == h->value.data + h->value.len || *p == ',' || *p == ' ') {
-            r->allow_trailers = 1;
-            return NGX_OK;
-        }
-    }
-
-    return NGX_OK;
-}
-
-
-static ngx_int_t
 ngx_http_process_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h,
     ngx_uint_t offset)
 {
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
index e9fa52f..f6f209b 100644
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -196,7 +196,6 @@
     ngx_table_elt_t                  *range;
     ngx_table_elt_t                  *if_range;
 
-    ngx_array_t                       te;
     ngx_table_elt_t                  *transfer_encoding;
     ngx_table_elt_t                  *expect;
     ngx_table_elt_t                  *upgrade;
@@ -516,7 +515,6 @@
     unsigned                          pipeline:1;
     unsigned                          chunked:1;
     unsigned                          header_only:1;
-    unsigned                          allow_trailers:1;
     unsigned                          expect_trailers:1;
     unsigned                          keepalive:1;
     unsigned                          lingering_close:1;
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
index 026458f..9e99da8 100644
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -2766,7 +2766,7 @@
     ngx_list_part_t  *part;
     ngx_table_elt_t  *h, *ho;
 
-    if (!u->conf->pass_trailers || !r->allow_trailers || !r->expect_trailers) {
+    if (!r->expect_trailers || !u->conf->pass_trailers) {
         return NGX_OK;
     }
 
@@ -5199,9 +5199,7 @@
 {
     ngx_table_elt_t  *ho;
 
-    if (!r->upstream->conf->pass_trailers
-        || !r->allow_trailers || !r->expect_trailers)
-    {
+    if (!r->expect_trailers || !r->upstream->conf->pass_trailers) {
         return NGX_OK;
     }
 
diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c
index 29b07b6..8268a01 100644
--- a/src/http/v2/ngx_http_v2_filter_module.c
+++ b/src/http/v2/ngx_http_v2_filter_module.c
@@ -50,7 +50,7 @@
 #define NGX_HTTP_V2_SERVER_INDEX          54
 #define NGX_HTTP_V2_VARY_INDEX            59
 
-#define NGX_HTTP_V2_FRAME_ERROR           (ngx_http_v2_out_frame_t *) -1
+#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,
@@ -133,12 +133,12 @@
     u_char                     status, *pos, *start, *p, *tmp;
     size_t                     len, tmp_len;
     ngx_str_t                  host, location;
-    ngx_uint_t                 i, port, fin;
+    ngx_uint_t                 i, port;
     ngx_list_part_t           *part;
     ngx_table_elt_t           *header;
     ngx_connection_t          *fc;
     ngx_http_cleanup_t        *cln;
-    ngx_http_v2_out_frame_t   *headers, *trailers;
+    ngx_http_v2_out_frame_t   *frame;
     ngx_http_core_loc_conf_t  *clcf;
     ngx_http_core_srv_conf_t  *cscf;
     u_char                     addr[NGX_SOCKADDR_STRLEN];
@@ -625,6 +625,15 @@
                                       header[i].value.len, tmp);
     }
 
+    frame = ngx_http_v2_create_headers_frame(r, start, pos, r->header_only);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_v2_queue_blocked_frame(r->stream->connection, frame);
+
+    r->stream->queued = 1;
+
     cln = ngx_http_cleanup_add(r, 0);
     if (cln == NULL) {
         return NGX_ERROR;
@@ -633,32 +642,6 @@
     cln->handler = ngx_http_v2_filter_cleanup;
     cln->data = r->stream;
 
-    if (r->header_only && r->allow_trailers && r->expect_trailers) {
-        trailers = ngx_http_v2_create_trailers_frame(r);
-        if (trailers == NGX_HTTP_V2_FRAME_ERROR) {
-            return NGX_ERROR;
-        }
-
-        fin = trailers ? 0 : 1;
-
-    } else {
-        trailers = NULL;
-        fin = r->header_only;
-    }
-
-    headers = ngx_http_v2_create_headers_frame(r, start, pos, fin);
-    if (headers == NULL) {
-        return NGX_ERROR;
-    }
-
-    ngx_http_v2_queue_blocked_frame(r->stream->connection, headers);
-    r->stream->queued = 1;
-
-    if (trailers) {
-        ngx_http_v2_queue_blocked_frame(r->stream->connection, trailers);
-        r->stream->queued++;
-    }
-
     fc->send_chain = ngx_http_v2_send_chain;
     fc->need_last_buf = 1;
 
@@ -669,16 +652,11 @@
 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;
-    ngx_http_v2_out_frame_t  *frame;
-
-    if (ngx_http_eval_trailers(r) != NGX_OK) {
-        return NGX_HTTP_V2_FRAME_ERROR;
-    }
+    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;
@@ -703,19 +681,17 @@
         }
 
         if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
-            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
                           "too long response trailer name: \"%V\"",
                           &header[i].key);
-
-            return NGX_HTTP_V2_FRAME_ERROR;
+            return NULL;
         }
 
         if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
-            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
                           "too long response trailer value: \"%V: %V\"",
                           &header[i].key, &header[i].value);
-
-            return NGX_HTTP_V2_FRAME_ERROR;
+            return NULL;
         }
 
         len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
@@ -731,14 +707,14 @@
     }
 
     if (len == 0) {
-        return NULL;
+        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 NGX_HTTP_V2_FRAME_ERROR;
+        return NULL;
     }
 
     start = pos;
@@ -758,10 +734,7 @@
             i = 0;
         }
 
-        if (header[i].hash == 0
-            || header[i].key.len > NGX_HTTP_V2_MAX_FIELD
-            || header[i].value.len > NGX_HTTP_V2_MAX_FIELD)
-        {
+        if (header[i].hash == 0) {
             continue;
         }
 
@@ -784,12 +757,7 @@
                                       header[i].value.len, tmp);
     }
 
-    frame = ngx_http_v2_create_headers_frame(r, start, pos, 1);
-    if (frame == NULL) {
-        return NGX_HTTP_V2_FRAME_ERROR;
-    }
-
-    return frame;
+    return ngx_http_v2_create_headers_frame(r, start, pos, 1);
 }
 
 
@@ -1029,7 +997,7 @@
     frame_size = (h2lcf->chunk_size < h2c->frame_size)
                  ? h2lcf->chunk_size : h2c->frame_size;
 
-    trailers = NULL;
+    trailers = NGX_HTTP_V2_NO_TRAILERS;
 
 #if (NGX_SUPPRESS_WARN)
     cl = NULL;
@@ -1093,19 +1061,20 @@
             size -= rest;
         }
 
-        if (cl->buf->last_buf && r->allow_trailers && r->expect_trailers) {
+        if (cl->buf->last_buf) {
             trailers = ngx_http_v2_create_trailers_frame(r);
-            if (trailers == NGX_HTTP_V2_FRAME_ERROR) {
+            if (trailers == NULL) {
                 return NGX_CHAIN_ERROR;
             }
 
-            if (trailers) {
+            if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
                 cl->buf->last_buf = 0;
             }
         }
 
         if (frame_size || cl->buf->last_buf) {
-            frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl);
+            frame = ngx_http_v2_filter_get_data_frame(stream, frame_size,
+                                                      out, cl);
             if (frame == NULL) {
                 return NGX_CHAIN_ERROR;
             }
@@ -1118,12 +1087,13 @@
             stream->queued++;
         }
 
-        if (trailers) {
-            ngx_http_v2_queue_frame(h2c, trailers);
-            stream->queued++;
-        }
-
         if (in == NULL) {
+
+            if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
+                ngx_http_v2_queue_frame(h2c, trailers);
+                stream->queued++;
+            }
+
             break;
         }