aboutsummaryrefslogtreecommitdiff
path: root/src/http
diff options
context:
space:
mode:
Diffstat (limited to 'src/http')
-rw-r--r--src/http/modules/ngx_http_addition_filter_module.c2
-rw-r--r--src/http/modules/ngx_http_charset_filter_module.c2
-rw-r--r--src/http/modules/ngx_http_fastcgi_module.c2
-rw-r--r--src/http/modules/ngx_http_grpc_module.c19
-rw-r--r--src/http/modules/ngx_http_gunzip_filter_module.c2
-rw-r--r--src/http/modules/ngx_http_gzip_filter_module.c2
-rw-r--r--src/http/modules/ngx_http_proxy_module.c41
-rw-r--r--src/http/modules/ngx_http_scgi_module.c2
-rw-r--r--src/http/modules/ngx_http_ssi_filter_module.c4
-rw-r--r--src/http/modules/ngx_http_sub_filter_module.c2
-rw-r--r--src/http/modules/ngx_http_uwsgi_module.c2
-rw-r--r--src/http/modules/ngx_http_xslt_filter_module.c2
-rw-r--r--src/http/ngx_http.c1
-rw-r--r--src/http/ngx_http.h2
-rw-r--r--src/http/ngx_http_core_module.c43
-rw-r--r--src/http/ngx_http_core_module.h2
-rw-r--r--src/http/ngx_http_header_filter_module.c107
-rw-r--r--src/http/ngx_http_request.h1
-rw-r--r--src/http/ngx_http_upstream.c159
-rw-r--r--src/http/ngx_http_upstream.h3
-rw-r--r--src/http/v2/ngx_http_v2.h1
-rw-r--r--src/http/v2/ngx_http_v2_filter_module.c202
-rw-r--r--src/http/v3/ngx_http_v3_filter_module.c150
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;