HTTP/2: added support for trailers in HTTP responses.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
This commit is contained in:
Piotr Sikora 2017-03-24 03:37:34 -07:00
parent cfdce50657
commit d85f2f9e92

View File

@ -50,13 +50,17 @@
#define NGX_HTTP_V2_SERVER_INDEX 54 #define NGX_HTTP_V2_SERVER_INDEX 54
#define NGX_HTTP_V2_VARY_INDEX 59 #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, static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
u_char *tmp, ngx_uint_t lower); u_char *tmp, ngx_uint_t lower);
static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
ngx_uint_t value); ngx_uint_t value);
static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( 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, static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc,
ngx_chain_t *in, off_t limit); ngx_chain_t *in, off_t limit);
@ -612,7 +616,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
header[i].value.len, tmp); 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) { if (frame == NULL) {
return NGX_ERROR; return NGX_ERROR;
} }
@ -636,6 +640,118 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
} }
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 * static u_char *
ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
ngx_uint_t lower) ngx_uint_t lower)
@ -686,7 +802,7 @@ ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
static ngx_http_v2_out_frame_t * static ngx_http_v2_out_frame_t *
ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, 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; u_char type, flags;
size_t rest, frame_size; size_t rest, frame_size;
@ -707,12 +823,12 @@ ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
frame->stream = stream; frame->stream = stream;
frame->length = rest; frame->length = rest;
frame->blocked = 1; frame->blocked = 1;
frame->fin = r->header_only; frame->fin = fin;
ll = &frame->first; ll = &frame->first;
type = NGX_HTTP_V2_HEADERS_FRAME; 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; frame_size = stream->connection->frame_size;
for ( ;; ) { for ( ;; ) {
@ -776,7 +892,7 @@ ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
continue; continue;
} }
b->last_buf = r->header_only; b->last_buf = fin;
cl->next = NULL; cl->next = NULL;
frame->last = cl; frame->last = cl;
@ -798,7 +914,7 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
ngx_http_request_t *r; ngx_http_request_t *r;
ngx_http_v2_stream_t *stream; ngx_http_v2_stream_t *stream;
ngx_http_v2_loc_conf_t *h2lcf; 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; ngx_http_v2_connection_t *h2c;
r = fc->data; r = fc->data;
@ -872,6 +988,8 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
frame_size = (h2lcf->chunk_size < h2c->frame_size) frame_size = (h2lcf->chunk_size < h2c->frame_size)
? h2lcf->chunk_size : h2c->frame_size; ? h2lcf->chunk_size : h2c->frame_size;
trailers = NGX_HTTP_V2_NO_TRAILERS;
#if (NGX_SUPPRESS_WARN) #if (NGX_SUPPRESS_WARN)
cl = NULL; cl = NULL;
#endif #endif
@ -934,19 +1052,39 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
size -= rest; size -= rest;
} }
frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl); if (cl->buf->last_buf) {
if (frame == NULL) { trailers = ngx_http_v2_create_trailers_frame(r);
return NGX_CHAIN_ERROR; 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; h2c->send_window -= frame_size;
stream->queued++;
stream->send_window -= frame_size;
stream->queued++;
}
if (in == NULL) { if (in == NULL) {
if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
ngx_http_v2_queue_frame(h2c, trailers);
stream->queued++;
}
break; break;
} }