diff options
author | Piotr Sikora <piotrsikora@google.com> | 2017-03-24 13:37:34 +0300 |
---|---|---|
committer | Piotr Sikora <piotrsikora@google.com> | 2017-03-24 13:37:34 +0300 |
commit | cfdce50657cb8d25058de3d677f03cdef0d5de51 (patch) | |
tree | 122dd12203f4dcf5a6c2438fe13f31af8d8bce82 /src/http | |
parent | fa0992ed295ba83f711ca3d1ba8fc1baaa5760ca (diff) |
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@google.com>
Diffstat (limited to 'src/http')
-rw-r--r-- | src/http/modules/ngx_http_chunked_filter_module.c | 152 | ||||
-rw-r--r-- | src/http/ngx_http_core_module.c | 7 | ||||
-rw-r--r-- | src/http/ngx_http_request.c | 8 | ||||
-rw-r--r-- | src/http/ngx_http_request.h | 2 | ||||
-rw-r--r-- | src/http/ngx_http_variables.c | 15 |
5 files changed, 157 insertions, 27 deletions
diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c index ac2e3e8f9..4d6fd3eed 100644 --- a/src/http/modules/ngx_http_chunked_filter_module.c +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -17,6 +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, + ngx_http_chunked_filter_ctx_t *ctx); static ngx_http_module_t ngx_http_chunked_filter_module_ctx = { @@ -69,27 +71,29 @@ ngx_http_chunked_header_filter(ngx_http_request_t *r) return ngx_http_next_header_filter(r); } - if (r->headers_out.content_length_n == -1) { - if (r->http_version < NGX_HTTP_VERSION_11) { - r->keepalive = 0; + if (r->headers_out.content_length_n == -1 + || r->expect_trailers) + { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - } else { - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + if (r->http_version >= NGX_HTTP_VERSION_11 + && clcf->chunked_transfer_encoding) + { + if (r->expect_trailers) { + ngx_http_clear_content_length(r); + } - if (clcf->chunked_transfer_encoding) { - r->chunked = 1; + r->chunked = 1; - ctx = ngx_pcalloc(r->pool, - sizeof(ngx_http_chunked_filter_ctx_t)); - if (ctx == NULL) { - return NGX_ERROR; - } + 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); + ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module); - } else { - r->keepalive = 0; - } + } else if (r->headers_out.content_length_n == -1) { + r->keepalive = 0; } } @@ -179,26 +183,17 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } 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; + tl->buf->pos += 2; } } else if (size > 0) { @@ -230,6 +225,109 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } +static ngx_chain_t * +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; + + 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; + } + + len += header[i].key.len + sizeof(": ") - 1 + + header[i].value.len + sizeof(CRLF) - 1; + } + + 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; + + 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->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; + + 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; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http trailer: \"%V: %V\"", + &header[i].key, &header[i].value); + + 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; + + return cl; +} + + static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf) { diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index b5be1ffeb..02059efd8 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -2485,6 +2485,13 @@ ngx_http_subrequest(ngx_http_request_t *r, return NGX_ERROR; } + if (ngx_list_init(&sr->headers_out.trailers, r->pool, 4, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); sr->main_conf = cscf->ctx->main_conf; sr->srv_conf = cscf->ctx->srv_conf; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index cc3722fb3..de1b20270 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -562,6 +562,14 @@ ngx_http_create_request(ngx_connection_t *c) return NULL; } + if (ngx_list_init(&r->headers_out.trailers, r->pool, 4, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_destroy_pool(r->pool); + return NULL; + } + r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module); if (r->ctx == NULL) { ngx_destroy_pool(r->pool); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 283c58294..f7f3e97f4 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -252,6 +252,7 @@ typedef struct { typedef struct { ngx_list_t headers; + ngx_list_t trailers; ngx_uint_t status; ngx_str_t status_line; @@ -514,6 +515,7 @@ struct ngx_http_request_s { unsigned pipeline:1; unsigned chunked:1; unsigned header_only:1; + unsigned expect_trailers:1; unsigned keepalive:1; unsigned lingering_close:1; unsigned discard_body:1; diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c index 6138819dd..cfb538a69 100644 --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c @@ -38,6 +38,8 @@ static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r, @@ -365,6 +367,9 @@ static ngx_http_variable_t ngx_http_core_variables[] = { { ngx_string("sent_http_"), NULL, ngx_http_variable_unknown_header_out, 0, NGX_HTTP_VAR_PREFIX, 0 }, + { ngx_string("sent_trailer_"), NULL, ngx_http_variable_unknown_trailer_out, + 0, NGX_HTTP_VAR_PREFIX, 0 }, + { ngx_string("cookie_"), NULL, ngx_http_variable_cookie, 0, NGX_HTTP_VAR_PREFIX, 0 }, @@ -934,6 +939,16 @@ ngx_http_variable_unknown_header_out(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + &r->headers_out.trailers.part, + sizeof("sent_trailer_") - 1); +} + + ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, ngx_list_part_t *part, size_t prefix) |