# HG changeset patch # User Ruslan Ermilov # Date 1518706292 -10800 # Node ID 3d2b0b02bd3d9a5c2b1f5d0451b4e6a1c0fef1f4 # Parent 33edea74bd588696042da5cd9608de5e8fd79bae HTTP/2: push additional request headers (closes #1478). The Accept-Encoding, Accept-Language, and User-Agent header fields are now copied from the original request to pushed requests. diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -420,6 +420,7 @@ if [ $HTTP = YES ]; then if [ $HTTP_V2 = YES ]; then have=NGX_HTTP_V2 . auto/have + have=NGX_HTTP_HEADERS . auto/have ngx_module_name=ngx_http_v2_module ngx_module_incs=src/http/v2 diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -11,6 +11,14 @@ #include +typedef struct { + ngx_str_t name; + ngx_uint_t offset; + ngx_uint_t hash; + ngx_http_header_t *hh; +} ngx_http_v2_parse_header_t; + + /* errors */ #define NGX_HTTP_V2_NO_ERROR 0x0 #define NGX_HTTP_V2_PROTOCOL_ERROR 0x1 @@ -156,6 +164,8 @@ static ngx_int_t ngx_http_v2_parse_schem ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value); +static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r, + ngx_http_v2_parse_header_t *header, ngx_str_t *value); static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header); @@ -201,6 +211,23 @@ static ngx_http_v2_handler_pt ngx_http_v (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) +static ngx_http_v2_parse_header_t ngx_http_v2_parse_headers[] = { + { ngx_string("host"), + offsetof(ngx_http_headers_in_t, host), 0, NULL }, + + { ngx_string("accept-encoding"), + offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, + + { ngx_string("accept-language"), + offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, + + { ngx_string("user-agent"), + offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, + + { ngx_null_string, 0, 0, NULL } +}; + + void ngx_http_v2_init(ngx_event_t *rev) { @@ -2514,21 +2541,25 @@ ngx_http_v2_parse_int(ngx_http_v2_connec } -ngx_int_t -ngx_http_v2_push_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t depend, - size_t request_length, ngx_str_t *path, ngx_str_t *authority) +ngx_http_v2_stream_t * +ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path) { - ngx_int_t rc; - ngx_str_t value; - ngx_connection_t *fc; - ngx_http_request_t *r; - ngx_http_v2_node_t *node; - ngx_http_v2_stream_t *stream; + ngx_int_t rc; + ngx_str_t value; + ngx_table_elt_t **h; + ngx_connection_t *fc; + ngx_http_request_t *r; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_connection_t *h2c; + ngx_http_v2_parse_header_t *header; + + h2c = parent->connection; node = ngx_http_v2_get_node_by_id(h2c, h2c->last_push, 1); if (node == NULL) { - return NGX_ERROR; + return NULL; } if (node->parent) { @@ -2538,19 +2569,17 @@ ngx_http_v2_push_stream(ngx_http_v2_conn stream = ngx_http_v2_create_stream(h2c, 1); if (stream == NULL) { - return NGX_ERROR; + return NULL; } stream->pool = ngx_create_pool(1024, h2c->connection->log); if (stream->pool == NULL) { - return NGX_ERROR; + return NULL; } r = stream->request; fc = r->connection; - r->request_length = request_length; - stream->in_closed = 1; stream->node = node; @@ -2559,10 +2588,10 @@ ngx_http_v2_push_stream(ngx_http_v2_conn ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 push stream sid:%ui " "depends on %ui excl:0 weight:16", - h2c->last_push, depend); + h2c->last_push, parent->node->id); node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT; - ngx_http_v2_set_dependency(h2c, node, depend, 0); + ngx_http_v2_set_dependency(h2c, node, parent->node->id, 0); r->method_name = ngx_http_core_get_method; r->method = NGX_HTTP_GET; @@ -2579,51 +2608,64 @@ ngx_http_v2_push_stream(ngx_http_v2_conn r->schema_end = r->schema_start + 4; } - value.len = authority->len; - - value.data = ngx_pstrdup(stream->pool, authority); + value.data = ngx_pstrdup(stream->pool, path); if (value.data == NULL) { - return NGX_ERROR; - } - - rc = ngx_http_v2_parse_authority(r, &value); - - if (rc != NGX_OK) { - goto error; + return NULL; } value.len = path->len; - value.data = ngx_pstrdup(stream->pool, path); - if (value.data == NULL) { - return NGX_ERROR; - } - rc = ngx_http_v2_parse_path(r, &value); if (rc != NGX_OK) { goto error; } + for (header = ngx_http_v2_parse_headers; header->name.len; header++) { + h = (ngx_table_elt_t **) + ((char *) &parent->request->headers_in + header->offset); + + if (*h == NULL) { + continue; + } + + value.len = (*h)->value.len; + + value.data = ngx_pnalloc(stream->pool, value.len + 1); + if (value.data == NULL) { + return NULL; + } + + ngx_memcpy(value.data, (*h)->value.data, value.len); + value.data[value.len] = '\0'; + + rc = ngx_http_v2_parse_header(r, header, &value); + + if (rc != NGX_OK) { + goto error; + } + } + fc->write->handler = ngx_http_v2_run_request_handler; ngx_post_event(fc->write, &ngx_posted_events); - return NGX_OK; + return stream; error: if (rc == NGX_ABORT) { - return NGX_ERROR; + /* header handler has already finalized request */ + return NULL; } if (rc == NGX_DECLINED) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; + return NULL; } (void) ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); - return NGX_ERROR; + return NULL; } @@ -3436,41 +3478,45 @@ ngx_http_v2_parse_scheme(ngx_http_reques static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) { + return ngx_http_v2_parse_header(r, &ngx_http_v2_parse_headers[0], value); +} + + +static ngx_int_t +ngx_http_v2_parse_header(ngx_http_request_t *r, + ngx_http_v2_parse_header_t *header, ngx_str_t *value) +{ ngx_table_elt_t *h; - ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; - static ngx_str_t host = ngx_string("host"); - h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { return NGX_ERROR; } - h->hash = ngx_hash_key(host.data, host.len); - - h->key.len = host.len; - h->key.data = host.data; + h->key.len = header->name.len; + h->key.data = header->name.data; + h->lowcase_key = header->name.data; + + if (header->hh == NULL) { + header->hash = ngx_hash_key(header->name.data, header->name.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, + h->lowcase_key, h->key.len); + if (header->hh == NULL) { + return NGX_ERROR; + } + } + + h->hash = header->hash; h->value.len = value->len; h->value.data = value->data; - h->lowcase_key = host.data; - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh == NULL) { - return NGX_ERROR; - } - - if (hh->handler(r, h, hh->offset) != NGX_OK) { - /* - * request has been finalized already - * in ngx_http_process_host() - */ + if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { + /* header handler has already finalized request */ return NGX_ABORT; } diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -283,9 +283,8 @@ void ngx_http_v2_init(ngx_event_t *rev); ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r); -ngx_int_t ngx_http_v2_push_stream(ngx_http_v2_connection_t *h2c, - ngx_uint_t depend, size_t request_length, ngx_str_t *path, - ngx_str_t *authority); +ngx_http_v2_stream_t *ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, + ngx_str_t *path); void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc); 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,20 +50,48 @@ #define NGX_HTTP_V2_STATUS_404_INDEX 13 #define NGX_HTTP_V2_STATUS_500_INDEX 14 +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 #define NGX_HTTP_V2_DATE_INDEX 33 #define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 #define NGX_HTTP_V2_LOCATION_INDEX 46 #define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 #define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +typedef struct { + ngx_str_t name; + u_char index; + ngx_uint_t offset; +} ngx_http_v2_push_header_t; + + +static ngx_http_v2_push_header_t ngx_http_v2_push_headers[] = { + { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX, + offsetof(ngx_http_headers_in_t, host) }, + + { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX, + offsetof(ngx_http_headers_in_t, accept_encoding) }, + + { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX, + offsetof(ngx_http_headers_in_t, accept_language) }, + + { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX, + offsetof(ngx_http_headers_in_t, user_agent) }, +}; + +#define NGX_HTTP_V2_PUSH_HEADERS \ + (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t)) + + static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_str_t *authority); + ngx_str_t *path, ngx_str_t *binary); static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_uint_t lower); @@ -685,16 +713,17 @@ ngx_http_v2_push_resources(ngx_http_requ { u_char *start, *end, *last; ngx_int_t rc; - ngx_str_t path, authority; + ngx_str_t path; ngx_uint_t i, push; ngx_table_elt_t **h; ngx_http_v2_loc_conf_t *h2lcf; ngx_http_complex_value_t *pushes; + ngx_str_t binary[NGX_HTTP_V2_PUSH_HEADERS]; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http2 push resources"); - ngx_str_null(&authority); + ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t)); h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); @@ -715,7 +744,7 @@ ngx_http_v2_push_resources(ngx_http_requ continue; } - rc = ngx_http_v2_push_resource(r, &path, &authority); + rc = ngx_http_v2_push_resource(r, &path, binary); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -880,7 +909,7 @@ ngx_http_v2_push_resources(ngx_http_requ if (push && path.len && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) { - rc = ngx_http_v2_push_resource(r, &path, &authority); + rc = ngx_http_v2_push_resource(r, &path, binary); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -905,15 +934,18 @@ ngx_http_v2_push_resources(ngx_http_requ static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_str_t *authority) + ngx_str_t *binary) { - u_char *start, *pos, *tmp; - size_t len; - ngx_table_elt_t *host; - ngx_connection_t *fc; - ngx_http_v2_stream_t *stream; - ngx_http_v2_out_frame_t *frame; - ngx_http_v2_connection_t *h2c; + u_char *start, *pos, *tmp; + size_t len; + ngx_str_t *value; + ngx_uint_t i; + ngx_table_elt_t **h; + ngx_connection_t *fc; + ngx_http_v2_stream_t *stream; + ngx_http_v2_out_frame_t *frame; + ngx_http_v2_connection_t *h2c; + ngx_http_v2_push_header_t *ph; fc = r->connection; @@ -944,42 +976,70 @@ ngx_http_v2_push_resource(ngx_http_reque return NGX_DECLINED; } - host = r->headers_in.host; - - if (host == NULL) { + if (r->headers_in.host == NULL) { return NGX_ABORT; } - if (authority->len == 0) { - - len = 1 + NGX_HTTP_V2_INT_OCTETS + host->value.len; + ph = ngx_http_v2_push_headers; + + if (binary[0].len) { + tmp = ngx_palloc(r->pool, path->len); + if (tmp == NULL) { + return NGX_ERROR; + } + + } else { + len = path->len; + + for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { + h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); + + if (*h) { + len = ngx_max(len, (*h)->value.len); + } + } tmp = ngx_palloc(r->pool, len); - pos = ngx_pnalloc(r->pool, len); - - if (pos == NULL || tmp == NULL) { + if (tmp == NULL) { return NGX_ERROR; } - authority->data = pos; - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); - pos = ngx_http_v2_write_value(pos, host->value.data, host->value.len, - tmp); - - authority->len = pos - authority->data; + for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { + h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); + + if (*h == NULL) { + continue; + } + + value = &(*h)->value; + + len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len; + + pos = ngx_pnalloc(r->pool, len); + if (pos == NULL) { + return NGX_ERROR; + } + + binary[i].data = pos; + + *pos++ = ngx_http_v2_inc_indexed(ph[i].index); + pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp); + + binary[i].len = pos - binary[i].data; + } } len = (h2c->table_update ? 1 : 0) + 1 + 1 + NGX_HTTP_V2_INT_OCTETS + path->len - + authority->len + 1; - tmp = ngx_palloc(r->pool, len); + for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { + len += binary[i].len; + } + pos = ngx_pnalloc(r->pool, len); - - if (pos == NULL || tmp == NULL) { + if (pos == NULL) { return NGX_ERROR; } @@ -1003,11 +1063,6 @@ ngx_http_v2_push_resource(ngx_http_reque *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":authority: %V\"", &host->value); - - pos = ngx_cpymem(pos, authority->data, authority->len); - #if (NGX_HTTP_SSL) if (fc->ssl) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -1022,6 +1077,20 @@ ngx_http_v2_push_resource(ngx_http_reque *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); } + for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { + h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); + + if (*h == NULL) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 push header: \"%V: %V\"", + &ph[i].name, &(*h)->value); + + pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + } + frame = ngx_http_v2_create_push_frame(r, start, pos); if (frame == NULL) { return NGX_ERROR; @@ -1031,8 +1100,14 @@ ngx_http_v2_push_resource(ngx_http_reque stream->queued++; - return ngx_http_v2_push_stream(h2c, stream->node->id, pos - start, - path, &host->value); + stream = ngx_http_v2_push_stream(stream, path); + + if (stream) { + stream->request->request_length = pos - start; + return NGX_OK; + } + + return NGX_ERROR; }