[PATCH 1 of 3] Added support for trailers in HTTP responses

Maxim Dounin mdounin at mdounin.ru
Mon Jun 5 16:29:40 UTC 2017


Hello!

On Fri, Jun 02, 2017 at 08:33:45PM -0700, Piotr Sikora via nginx-devel wrote:

> # HG changeset patch
> # User Piotr Sikora <piotrsikora at google.com>
> # Date 1490351854 25200
> #      Fri Mar 24 03:37:34 2017 -0700
> # Node ID 41c09a2fd90410e25ad8515793bd48028001c954
> # Parent  716852cce9136d977b81a2d1b8b6f9fbca0dce49
> Added support for trailers in HTTP responses.
> 
> Example:
> 
>    ngx_table_elt_t  *h;
> 
>    h = ngx_list_push(&r->headers_out.trailers);
>    if (h == NULL) {
>        return NGX_ERROR;
>    }
> 
>    ngx_str_set(&h->key, "Fun");
>    ngx_str_set(&h->value, "with trailers");
>    h->hash = ngx_hash_key_lc(h->key.data, h->key.len);
> 
> The code above adds "Fun: with trailers" trailer to the response.
> 
> Modules that want to emit trailers must set r->expect_trailers = 1
> in header filter, otherwise they might not be emitted for HTTP/1.1
> responses that aren't already chunked.
> 
> This change also adds $sent_trailer_* variables.
> 
> Signed-off-by: Piotr Sikora <piotrsikora at google.com>

Overall looks good, see some additional comments below.

> 
> diff -r 716852cce913 -r 41c09a2fd904 src/http/modules/ngx_http_chunked_filter_module.c
> --- a/src/http/modules/ngx_http_chunked_filter_module.c
> +++ b/src/http/modules/ngx_http_chunked_filter_module.c
> @@ -17,6 +17,7 @@ typedef struct {
>  
>  
>  static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
> +static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r);
>  
>  
>  static ngx_http_module_t  ngx_http_chunked_filter_module_ctx = {
> @@ -69,27 +70,28 @@ ngx_http_chunked_header_filter(ngx_http_
>          return ngx_http_next_header_filter(r);
>      }
>  
> -    if (r->headers_out.content_length_n == -1) {
> -        if (r->http_version < NGX_HTTP_VERSION_11) {
> +    if (r->headers_out.content_length_n == -1 || r->expect_trailers) {
> +

I actually think that using two lines as initially suggested is 
more readable and more in line with current style.  YMMV.

-    if (r->headers_out.content_length_n == -1 || r->expect_trailers) {
-
+    if (r->headers_out.content_length_n == -1
+        || r->expect_trailers)
+    {

[...]

> @@ -230,6 +223,105 @@ ngx_http_chunked_body_filter(ngx_http_re
>  }
>  
>  
> +static ngx_chain_t *
> +ngx_http_chunked_create_trailers(ngx_http_request_t *r)
> +{

[...]

> +        len += header[i].key.len + sizeof(": ") - 1
> +               + header[i].value.len + sizeof(CRLF) - 1;
> +    }
> +
> +    ctx = ngx_http_get_module_ctx(r, ngx_http_chunked_filter_module);

Usual approach is to pass context into internal functions if 
needed and already available in the calling functions.

 static ngx_chain_t *
-ngx_http_chunked_create_trailers(ngx_http_request_t *r)
+ngx_http_chunked_create_trailers(ngx_http_request_t *r,
+    ngx_http_chunked_filter_ctx_t *ctx)
 {

(and more, see full patch below)

> +
> +    cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
> +    if (cl == NULL) {
> +        return NULL;
> +    }
> +
> +    b = cl->buf;
> +
> +    b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
> +    b->temporary = 0;
> +    b->memory = 1;
> +    b->last_buf = 1;
> +
> +    b->start = ngx_palloc(r->pool, len);
> +    if (b->start == NULL) {
> +        return NULL;
> +    }

I would prefer to preserve the typical code path (when there are no 
trailers) without an extra allocation.  It looks like it would be 
as trivail as:

@@ -273,14 +273,18 @@ ngx_http_chunked_create_trailers(ngx_htt
     b->memory = 1;
     b->last_buf = 1;
 
+    if (len == sizeof(CRLF "0" CRLF CRLF) - 1) {
+        b->pos = (u_char *) CRLF "0" CRLF CRLF;
+        b->last = b->pos + sizeof(CRLF "0" CRLF CRLF) - 1;
+        return cl;
+    }
+
     b->start = ngx_palloc(r->pool, len);
     if (b->start == NULL) {
         return NULL;
     }

Note well that b->start is intentionally not touched in the 
previous code.  As buffers are reused, b->start, if set, is 
expected to point to a chunk of memory big enough to contain 
"0000000000000000" CRLF, as allocated in the 
ngx_http_chunked_body_filter().

While this is not critical in this particular code path, as 
last-chunk is expected to be only created once, the resulting code 
is confusing: while it provides b->tag to make the buffer 
reusable, it doesn't maintain required invariant on b->start.

Trivial solution would be to avoid setting b->start / b->end as it 
was done in the previous code and still done in the CRLF case.

-    b->start = ngx_palloc(r->pool, len);
-    if (b->start == NULL) {
+    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;


Full patch with the above comments:

diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c
--- a/src/http/modules/ngx_http_chunked_filter_module.c
+++ b/src/http/modules/ngx_http_chunked_filter_module.c
@@ -17,7 +17,8 @@ typedef struct {
 
 
 static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
-static ngx_chain_t *ngx_http_chunked_create_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,8 +71,9 @@ ngx_http_chunked_header_filter(ngx_http_
         return ngx_http_next_header_filter(r);
     }
 
-    if (r->headers_out.content_length_n == -1 || r->expect_trailers) {
-
+    if (r->headers_out.content_length_n == -1
+        || r->expect_trailers)
+    {
         clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 
         if (r->http_version >= NGX_HTTP_VERSION_11
@@ -181,7 +183,7 @@ ngx_http_chunked_body_filter(ngx_http_re
     }
 
     if (cl->buf->last_buf) {
-        tl = ngx_http_chunked_create_trailers(r);
+        tl = ngx_http_chunked_create_trailers(r, ctx);
         if (tl == NULL) {
             return NGX_ERROR;
         }
@@ -224,15 +226,15 @@ ngx_http_chunked_body_filter(ngx_http_re
 
 
 static ngx_chain_t *
-ngx_http_chunked_create_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;
+    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 = sizeof(CRLF "0" CRLF CRLF) - 1;
 
@@ -259,8 +261,6 @@ ngx_http_chunked_create_trailers(ngx_htt
                + header[i].value.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;
@@ -273,14 +273,18 @@ ngx_http_chunked_create_trailers(ngx_htt
     b->memory = 1;
     b->last_buf = 1;
 
-    b->start = ngx_palloc(r->pool, len);
-    if (b->start == NULL) {
+    if (len == sizeof(CRLF "0" CRLF CRLF) - 1) {
+        b->pos = (u_char *) CRLF "0" CRLF CRLF;
+        b->last = b->pos + sizeof(CRLF "0" CRLF CRLF) - 1;
+        return cl;
+    }
+
+    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';

-- 
Maxim Dounin
http://nginx.org/


More information about the nginx-devel mailing list