# HG changeset patch # User Piotr Sikora # Date 1490351854 25200 # Node ID 4e784e095a9753fb2794d32b3ba3148afdabb68c # Parent 1b068a4e82d80effa6f915d90064462e1b55ab41 HTTP/2: added support for trailers in HTTP responses. Signed-off-by: Piotr Sikora diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/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_CRIT, 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_CRIT, 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); + + h2c->send_window -= frame_size; - stream->send_window -= frame_size; - stream->queued++; + 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; }