diff options
Diffstat (limited to 'src/http')
23 files changed, 727 insertions, 26 deletions
diff --git a/src/http/modules/ngx_http_addition_filter_module.c b/src/http/modules/ngx_http_addition_filter_module.c index e546f0d60..040244c7e 100644 --- a/src/http/modules/ngx_http_addition_filter_module.c +++ b/src/http/modules/ngx_http_addition_filter_module.c @@ -245,7 +245,7 @@ ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_charset_filter_module.c b/src/http/modules/ngx_http_charset_filter_module.c index d44da6233..482960040 100644 --- a/src/http/modules/ngx_http_charset_filter_module.c +++ b/src/http/modules/ngx_http_charset_filter_module.c @@ -1564,7 +1564,7 @@ ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_charset_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index a41b496dc..6b1977340 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -3130,7 +3130,7 @@ ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_fastcgi_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 80046d6a4..4c95525b4 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -1869,7 +1869,8 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) return NGX_HTTP_UPSTREAM_INVALID_HEADER; } - if (status < NGX_HTTP_OK) { + if (status < NGX_HTTP_OK && status != NGX_HTTP_EARLY_HINTS) + { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected :status \"%V\"", status_line); @@ -1902,6 +1903,10 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) h->lowcase_key = h->key.data; h->hash = ngx_hash_key(h->key.data, h->key.len); + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + continue; + } + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); @@ -1923,6 +1928,17 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header done"); + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + if (ctx->end_stream) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + ctx->status = 0; + return NGX_HTTP_UPSTREAM_EARLY_HINTS; + } + if (ctx->end_stream) { u->headers_in.content_length_n = 0; @@ -4413,6 +4429,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) conf->upstream.pass_request_body = 1; conf->upstream.force_ranges = 0; conf->upstream.pass_trailers = 1; + conf->upstream.pass_early_hints = 1; conf->upstream.preserve_output = 1; conf->headers_source = NGX_CONF_UNSET_PTR; diff --git a/src/http/modules/ngx_http_gunzip_filter_module.c b/src/http/modules/ngx_http_gunzip_filter_module.c index 5d170a1ba..22e75e300 100644 --- a/src/http/modules/ngx_http_gunzip_filter_module.c +++ b/src/http/modules/ngx_http_gunzip_filter_module.c @@ -304,7 +304,7 @@ ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r, { int rc; - ctx->zstream.next_in = Z_NULL; + ctx->zstream.next_in = NULL; ctx->zstream.avail_in = 0; ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc; diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c index 7113df695..dac21bb18 100644 --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -1115,7 +1115,7 @@ ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index d4c5abf62..8d5385c1d 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -1888,6 +1888,13 @@ ngx_http_proxy_process_status_line(ngx_http_request_t *r) u->headers_in.status_n, &u->headers_in.status_line); if (ctx->status.http_version < NGX_HTTP_VERSION_11) { + + if (ctx->status.code == NGX_HTTP_EARLY_HINTS) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent HTTP/1.0 response with early hints"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + u->headers_in.connection_close = 1; } @@ -1949,6 +1956,14 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%V: %V\"", + &h->key, &h->value); + + if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + continue; + } + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); @@ -1960,10 +1975,6 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) } } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http proxy header: \"%V: %V\"", - &h->key, &h->value); - continue; } @@ -1974,6 +1985,20 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header done"); + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + ctx->status.code = 0; + ctx->status.count = 0; + ctx->status.start = NULL; + ctx->status.end = NULL; + + r->upstream->process_header = + ngx_http_proxy_process_status_line; + r->state = 0; + return NGX_HTTP_UPSTREAM_EARLY_HINTS; + } + /* * if no "Server" and "Date" in header line, * then add the special empty headers @@ -2021,8 +2046,6 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) * connections alive in case of r->header_only or X-Accel-Redirect */ - ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); - if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED || ctx->head @@ -3628,10 +3651,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif - /* "proxy_cyclic_temp_file" is disabled */ + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; + conf->upstream.pass_early_hints = 1; conf->headers_source = NGX_CONF_UNSET_PTR; @@ -3844,7 +3867,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_proxy_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index 9023a36e4..49977b07b 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -1543,7 +1543,7 @@ ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_scgi_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c index 47068f755..65ca03440 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/http/modules/ngx_http_ssi_filter_module.c @@ -820,7 +820,7 @@ ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } for (prm = cmd->params; prm->name.len; prm++) { - if (prm->mandatory && params[prm->index] == 0) { + if (prm->mandatory && params[prm->index] == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "mandatory \"%V\" parameter is absent " "in \"%V\" SSI command", @@ -2942,7 +2942,7 @@ ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_sub_filter_module.c b/src/http/modules/ngx_http_sub_filter_module.c index 456bb27e3..b50a25235 100644 --- a/src/http/modules/ngx_http_sub_filter_module.c +++ b/src/http/modules/ngx_http_sub_filter_module.c @@ -901,7 +901,7 @@ ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index 51a861d9a..c1d0035cc 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -1803,7 +1803,7 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_uwsgi_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c index 8afd656af..4e6e1b99d 100644 --- a/src/http/modules/ngx_http_xslt_filter_module.c +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -1112,7 +1112,7 @@ ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_xslt_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index d835f896e..7f2b4225a 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -72,6 +72,7 @@ ngx_uint_t ngx_http_max_module; ngx_http_output_header_filter_pt ngx_http_top_header_filter; +ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter; ngx_http_output_body_filter_pt ngx_http_top_body_filter; ngx_http_request_body_filter_pt ngx_http_top_request_body_filter; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index cb4a1e68a..7d98f5cd7 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -152,6 +152,7 @@ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_int_t ngx_http_read_unbuffered_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_send_header(ngx_http_request_t *r); +ngx_int_t ngx_http_send_early_hints(ngx_http_request_t *r); ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error); ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r, @@ -191,6 +192,7 @@ extern ngx_str_t ngx_http_html_default_types[]; extern ngx_http_output_header_filter_pt ngx_http_top_header_filter; +extern ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter; extern ngx_http_output_body_filter_pt ngx_http_top_body_filter; extern ngx_http_request_body_filter_pt ngx_http_top_request_body_filter; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 92c3eae8a..c75ddb849 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -670,6 +670,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_loc_conf_t, etag), NULL }, + { ngx_string("early_hints"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_http_set_predicate_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, early_hints), + NULL }, + { ngx_string("error_page"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_2MORE, @@ -1858,6 +1865,37 @@ ngx_http_send_header(ngx_http_request_t *r) ngx_int_t +ngx_http_send_early_hints(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_core_loc_conf_t *clcf; + + if (r->post_action) { + return NGX_OK; + } + + if (r->header_sent) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "header already sent"); + return NGX_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rc = ngx_http_test_predicates(r, clcf->early_hints); + + if (rc != NGX_DECLINED) { + return rc; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http send early hints \"%V?%V\"", &r->uri, &r->args); + + return ngx_http_top_early_hints_filter(r); +} + + +ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; @@ -3637,6 +3675,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) clcf->chunked_transfer_encoding = NGX_CONF_UNSET; clcf->etag = NGX_CONF_UNSET; clcf->server_tokens = NGX_CONF_UNSET_UINT; + clcf->early_hints = NGX_CONF_UNSET_PTR; clcf->types_hash_max_size = NGX_CONF_UNSET_UINT; clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT; @@ -3892,7 +3931,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->client_body_temp_path, prev->client_body_temp_path, &ngx_http_client_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } @@ -3917,6 +3956,8 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->server_tokens, prev->server_tokens, NGX_HTTP_SERVER_TOKENS_ON); + ngx_conf_merge_ptr_value(conf->early_hints, prev->early_hints, NULL); + ngx_conf_merge_ptr_value(conf->open_file_cache, prev->open_file_cache, NULL); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index e7e266bf8..a794144aa 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -430,6 +430,8 @@ struct ngx_http_core_loc_conf_s { ngx_http_complex_value_t *disable_symlinks_from; #endif + ngx_array_t *early_hints; /* early_hints */ + ngx_array_t *error_pages; /* error_page */ ngx_path_t *client_body_temp_path; /* client_body_temp_path */ diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index 76f6e9629..789938329 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -13,6 +13,7 @@ static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf); static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_early_hints_filter(ngx_http_request_t *r); static ngx_http_module_t ngx_http_header_filter_module_ctx = { @@ -50,6 +51,9 @@ static u_char ngx_http_server_string[] = "Server: nginx" CRLF; static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF; static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF; +static ngx_str_t ngx_http_early_hints_status_line = + ngx_string("HTTP/1.1 103 Early Hints" CRLF); + static ngx_str_t ngx_http_status_lines[] = { @@ -626,9 +630,112 @@ ngx_http_header_filter(ngx_http_request_t *r) static ngx_int_t +ngx_http_early_hints_filter(ngx_http_request_t *r) +{ + size_t len; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t out; + ngx_list_part_t *part; + ngx_table_elt_t *header; + + if (r != r->main) { + return NGX_OK; + } + + if (r->http_version < NGX_HTTP_VERSION_11) { + return NGX_OK; + } + + len = 0; + + part = &r->headers_out.headers.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; + } + + if (len == 0) { + return NGX_OK; + } + + len += ngx_http_early_hints_status_line.len + /* the end of the early hints */ + + sizeof(CRLF) - 1; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = ngx_copy(b->last, ngx_http_early_hints_status_line.data, + ngx_http_early_hints_status_line.len); + + part = &r->headers_out.headers.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; + } + + 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; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "%*s", (size_t) (b->last - b->pos), b->pos); + + /* the end of HTTP early hints */ + *b->last++ = CR; *b->last++ = LF; + + r->header_size = b->last - b->pos; + + b->flush = 1; + + out.buf = b; + out.next = NULL; + + return ngx_http_write_filter(r, &out); +} + + +static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf) { ngx_http_top_header_filter = ngx_http_header_filter; + ngx_http_top_early_hints_filter = ngx_http_early_hints_filter; return NGX_OK; } diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 9407f46ae..ad11f147f 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -74,6 +74,7 @@ #define NGX_HTTP_CONTINUE 100 #define NGX_HTTP_SWITCHING_PROTOCOLS 101 #define NGX_HTTP_PROCESSING 102 +#define NGX_HTTP_EARLY_HINTS 103 #define NGX_HTTP_OK 200 #define NGX_HTTP_CREATED 201 diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index d4cf1b7fe..de0f92a4f 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -48,6 +48,9 @@ static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r); static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_process_early_hints(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_early_hints_writer(ngx_http_request_t *r); static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, @@ -1120,7 +1123,7 @@ ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u) return NGX_ERROR; } - if (rc == NGX_AGAIN) { + if (rc == NGX_AGAIN || rc == NGX_HTTP_UPSTREAM_EARLY_HINTS) { rc = NGX_HTTP_UPSTREAM_INVALID_HEADER; } @@ -2530,6 +2533,18 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) continue; } + if (rc == NGX_HTTP_UPSTREAM_EARLY_HINTS) { + rc = ngx_http_upstream_process_early_hints(r, u); + + if (rc == NGX_OK) { + rc = u->process_header(r); + + if (rc == NGX_AGAIN) { + continue; + } + } + } + break; } @@ -2568,6 +2583,148 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) static ngx_int_t +ngx_http_upstream_process_early_hints(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + u_char *p; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h, *ho; + ngx_connection_t *c; + + c = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream early hints"); + + if (u->conf->pass_early_hints) { + + u->early_hints_length += u->buffer.pos - u->buffer.start; + + if (u->early_hints_length <= (off_t) u->conf->buffer_size) { + + part = &u->headers_in.headers.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len)) + { + continue; + } + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = h[i]; + } + + if (ngx_http_send_early_hints(r) == NGX_ERROR) { + return NGX_ERROR; + } + + if (c->buffered) { + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + r->write_event_handler = ngx_http_upstream_early_hints_writer; + } + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "upstream sent too big early hints"); + } + } + + ngx_http_clean_header(r); + + ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); + u->headers_in.content_length_n = -1; + u->headers_in.last_modified_time = -1; + + if (ngx_list_init(&u->headers_in.headers, r->pool, 8, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_list_init(&u->headers_in.trailers, r->pool, 2, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + p = u->buffer.pos; + + u->buffer.pos = u->buffer.start; + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + u->buffer.pos += r->cache->header_start; + } + +#endif + + u->buffer.last = ngx_movemem(u->buffer.pos, p, u->buffer.last - p); + + return NGX_OK; +} + + +static void +ngx_http_upstream_early_hints_writer(ngx_http_request_t *r) +{ + ngx_connection_t *c; + ngx_http_upstream_t *u; + + c = r->connection; + u = r->upstream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream early hints writer"); + + c->log->action = "sending early hints to client"; + + if (ngx_http_write_filter(r, NULL) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (!c->buffered) { + if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { + r->write_event_handler = + ngx_http_upstream_wr_check_broken_connection; + + } else { + r->write_event_handler = ngx_http_request_empty_handler; + } + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } +} + + +static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_msec_t timeout; diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index e0a903669..f3e9f7979 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -43,6 +43,7 @@ |NGX_HTTP_UPSTREAM_FT_HTTP_429) #define NGX_HTTP_UPSTREAM_INVALID_HEADER 40 +#define NGX_HTTP_UPSTREAM_EARLY_HINTS 41 #define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 @@ -185,6 +186,7 @@ typedef struct { ngx_flag_t pass_request_headers; ngx_flag_t pass_request_body; ngx_flag_t pass_trailers; + ngx_flag_t pass_early_hints; ngx_flag_t ignore_client_abort; ngx_flag_t intercept_errors; @@ -354,6 +356,7 @@ struct ngx_http_upstream_s { ngx_buf_t buffer; off_t length; + off_t early_hints_length; ngx_chain_t *out_bufs; ngx_chain_t *busy_bufs; diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 6751b3026..9605c8a23 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -213,6 +213,7 @@ struct ngx_http_v2_stream_s { ngx_pool_t *pool; + unsigned initialized:1; unsigned waiting:1; unsigned blocked:1; unsigned exhausted:1; diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index b63e343a0..907906a88 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -27,6 +27,10 @@ #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static ngx_int_t ngx_http_v2_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_early_hints_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_init_stream(ngx_http_request_t *r); + 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_uint_t fin); static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame( @@ -95,6 +99,7 @@ ngx_module_t ngx_http_v2_filter_module = { static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_header_filter_pt ngx_http_next_early_hints_filter; static ngx_int_t @@ -107,7 +112,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *fc; - ngx_http_cleanup_t *cln; ngx_http_v2_stream_t *stream; ngx_http_v2_out_frame_t *frame; ngx_http_v2_connection_t *h2c; @@ -612,7 +616,196 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) ngx_http_v2_queue_blocked_frame(h2c, frame); - stream->queued = 1; + stream->queued++; + + if (ngx_http_v2_init_stream(r) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_http_v2_filter_send(fc, stream); +} + + +static ngx_int_t +ngx_http_v2_early_hints_filter(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; + ngx_connection_t *fc; + ngx_http_v2_stream_t *stream; + ngx_http_v2_out_frame_t *frame; + ngx_http_v2_connection_t *h2c; + + stream = r->stream; + + if (!stream) { + return ngx_http_next_early_hints_filter(r); + } + + if (r != r->main) { + return NGX_OK; + } + + fc = r->connection; + + if (fc->error) { + return NGX_ERROR; + } + + len = 0; + tmp_len = 0; + + part = &r->headers_out.headers.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, fc->log, 0, + "too long response header name: \"%V\"", + &header[i].key); + return NGX_ERROR; + } + + if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_CRIT, fc->log, 0, + "too long response header value: \"%V: %V\"", + &header[i].key, &header[i].value); + return NGX_ERROR; + } + + 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_OK; + } + + h2c = stream->connection; + + len += h2c->table_update ? 1 : 0; + len += 1 + ngx_http_v2_literal_size("418"); + + tmp = ngx_palloc(r->pool, tmp_len); + pos = ngx_pnalloc(r->pool, len); + + if (pos == NULL || tmp == NULL) { + return NGX_ERROR; + } + + start = pos; + + if (h2c->table_update) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 table size update: 0"); + *pos++ = (1 << 5) | 0; + h2c->table_update = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \":status: %03ui\"", + (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); + *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; + pos = ngx_sprintf(pos, "%03ui", (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + part = &r->headers_out.headers.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 (fc->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, fc->log, 0, + "http2 output header: \"%*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); + } + + frame = ngx_http_v2_create_headers_frame(r, start, pos, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + stream->queued++; + + if (ngx_http_v2_init_stream(r) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_http_v2_filter_send(fc, stream); +} + + +static ngx_int_t +ngx_http_v2_init_stream(ngx_http_request_t *r) +{ + ngx_connection_t *fc; + ngx_http_cleanup_t *cln; + ngx_http_v2_stream_t *stream; + + stream = r->stream; + fc = r->connection; + + if (stream->initialized) { + return NGX_OK; + } + + stream->initialized = 1; cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { @@ -626,7 +819,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) fc->need_last_buf = 1; fc->need_flush_buf = 1; - return ngx_http_v2_filter_send(fc, stream); + return NGX_OK; } @@ -1567,5 +1760,8 @@ ngx_http_v2_filter_init(ngx_conf_t *cf) ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_v2_header_filter; + ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter; + ngx_http_top_early_hints_filter = ngx_http_v2_early_hints_filter; + return NGX_OK; } diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 4d2276dc0..e3f15368f 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -20,6 +20,7 @@ #define NGX_HTTP_V3_HEADER_METHOD_GET 17 #define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 #define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_103 24 #define NGX_HTTP_V3_HEADER_STATUS_200 25 #define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 #define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 @@ -36,6 +37,7 @@ typedef struct { static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_early_hints_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, @@ -75,6 +77,7 @@ ngx_module_t ngx_http_v3_filter_module = { static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_header_filter_pt ngx_http_next_early_hints_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; @@ -589,6 +592,150 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) static ngx_int_t +ngx_http_v3_early_hints_filter(ngx_http_request_t *r) +{ + size_t len, n; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *out, *hl, *cl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_early_hints_filter(r); + } + + if (r != r->main) { + return NGX_OK; + } + + len = 0; + + part = &r->headers_out.headers.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 += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + if (len == 0) { + return NGX_OK; + } + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_STATUS_103); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output header: \":status: %03ui\"", + (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_103); + + part = &r->headers_out.headers.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, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + b->flush = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + h3c = ngx_http_v3_get_session(r->connection); + h3c->payload_bytes += n; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + out = hl; + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + r->header_size += cl->buf->last - cl->buf->pos; + } + + return ngx_http_write_filter(r, out); +} + + +static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; @@ -845,6 +992,9 @@ ngx_http_v3_filter_init(ngx_conf_t *cf) ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_v3_header_filter; + ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter; + ngx_http_top_early_hints_filter = ngx_http_v3_early_hints_filter; + ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_v3_body_filter; |