Upstream: early hints support.
Some checks failed
buildbot / buildbot (push) Has been cancelled

The change implements processing upstream early hints response in
ngx_http_proxy_module and ngx_http_grpc_module.  A new directive
"early_hints" enables sending early hints to the client.  By default,
sending early hints is disabled.

Example:

    map $http_sec_fetch_mode $early_hints {
        navigate $http2$http3;
    }

    early_hints $early_hints;

    proxy_pass http://example.com;
This commit is contained in:
Roman Arutyunyan 2024-11-15 08:23:53 +04:00 committed by Roman Arutyunyan
parent ea001feb10
commit 662c1dd2a9
13 changed files with 698 additions and 10 deletions

View File

@ -1869,7 +1869,8 @@ ngx_http_grpc_process_header(ngx_http_request_t *r)
return NGX_HTTP_UPSTREAM_INVALID_HEADER; 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, ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"upstream sent unexpected :status \"%V\"", "upstream sent unexpected :status \"%V\"",
status_line); status_line);
@ -1902,6 +1903,10 @@ ngx_http_grpc_process_header(ngx_http_request_t *r)
h->lowcase_key = h->key.data; h->lowcase_key = h->key.data;
h->hash = ngx_hash_key(h->key.data, h->key.len); 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, hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len); h->lowcase_key, h->key.len);
@ -4413,6 +4418,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf)
conf->upstream.pass_request_body = 1; conf->upstream.pass_request_body = 1;
conf->upstream.force_ranges = 0; conf->upstream.force_ranges = 0;
conf->upstream.pass_trailers = 1; conf->upstream.pass_trailers = 1;
conf->upstream.pass_early_hints = 1;
conf->upstream.preserve_output = 1; conf->upstream.preserve_output = 1;
conf->headers_source = NGX_CONF_UNSET_PTR; conf->headers_source = NGX_CONF_UNSET_PTR;

View File

@ -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); u->headers_in.status_n, &u->headers_in.status_line);
if (ctx->status.http_version < NGX_HTTP_VERSION_11) { 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; 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_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, hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len); 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; continue;
} }
@ -1974,6 +1985,10 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http proxy header done"); "http proxy header done");
if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
return NGX_OK;
}
/* /*
* if no "Server" and "Date" in header line, * if no "Server" and "Date" in header line,
* then add the special empty headers * then add the special empty headers
@ -3628,10 +3643,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR;
#endif #endif
/* "proxy_cyclic_temp_file" is disabled */ /* the hardcoded values */
conf->upstream.cyclic_temp_file = 0; conf->upstream.cyclic_temp_file = 0;
conf->upstream.change_buffering = 1; conf->upstream.change_buffering = 1;
conf->upstream.pass_early_hints = 1;
conf->headers_source = NGX_CONF_UNSET_PTR; conf->headers_source = NGX_CONF_UNSET_PTR;

View File

@ -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_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_output_body_filter_pt ngx_http_top_body_filter;
ngx_http_request_body_filter_pt ngx_http_top_request_body_filter; ngx_http_request_body_filter_pt ngx_http_top_request_body_filter;

View File

@ -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_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_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 ngx_http_special_response_handler(ngx_http_request_t *r,
ngx_int_t error); ngx_int_t error);
ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r, 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_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_output_body_filter_pt ngx_http_top_body_filter;
extern ngx_http_request_body_filter_pt ngx_http_top_request_body_filter; extern ngx_http_request_body_filter_pt ngx_http_top_request_body_filter;

View File

@ -670,6 +670,13 @@ static ngx_command_t ngx_http_core_commands[] = {
offsetof(ngx_http_core_loc_conf_t, etag), offsetof(ngx_http_core_loc_conf_t, etag),
NULL }, 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_string("error_page"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|NGX_CONF_2MORE, |NGX_CONF_2MORE,
@ -1857,6 +1864,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_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{ {
@ -3637,6 +3675,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf)
clcf->chunked_transfer_encoding = NGX_CONF_UNSET; clcf->chunked_transfer_encoding = NGX_CONF_UNSET;
clcf->etag = NGX_CONF_UNSET; clcf->etag = NGX_CONF_UNSET;
clcf->server_tokens = NGX_CONF_UNSET_UINT; 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_max_size = NGX_CONF_UNSET_UINT;
clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT; clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT;
@ -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_conf_merge_uint_value(conf->server_tokens, prev->server_tokens,
NGX_HTTP_SERVER_TOKENS_ON); 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, ngx_conf_merge_ptr_value(conf->open_file_cache,
prev->open_file_cache, NULL); prev->open_file_cache, NULL);

View File

@ -430,6 +430,8 @@ struct ngx_http_core_loc_conf_s {
ngx_http_complex_value_t *disable_symlinks_from; ngx_http_complex_value_t *disable_symlinks_from;
#endif #endif
ngx_array_t *early_hints; /* early_hints */
ngx_array_t *error_pages; /* error_page */ ngx_array_t *error_pages; /* error_page */
ngx_path_t *client_body_temp_path; /* client_body_temp_path */ ngx_path_t *client_body_temp_path; /* client_body_temp_path */

View File

@ -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_init(ngx_conf_t *cf);
static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r); 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 = { 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_full_string[] = "Server: " NGINX_VER CRLF;
static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD 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[] = { static ngx_str_t ngx_http_status_lines[] = {
@ -625,10 +629,113 @@ 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 static ngx_int_t
ngx_http_header_filter_init(ngx_conf_t *cf) ngx_http_header_filter_init(ngx_conf_t *cf)
{ {
ngx_http_top_header_filter = ngx_http_header_filter; ngx_http_top_header_filter = ngx_http_header_filter;
ngx_http_top_early_hints_filter = ngx_http_early_hints_filter;
return NGX_OK; return NGX_OK;
} }

View File

@ -74,6 +74,7 @@
#define NGX_HTTP_CONTINUE 100 #define NGX_HTTP_CONTINUE 100
#define NGX_HTTP_SWITCHING_PROTOCOLS 101 #define NGX_HTTP_SWITCHING_PROTOCOLS 101
#define NGX_HTTP_PROCESSING 102 #define NGX_HTTP_PROCESSING 102
#define NGX_HTTP_EARLY_HINTS 103
#define NGX_HTTP_OK 200 #define NGX_HTTP_OK 200
#define NGX_HTTP_CREATED 201 #define NGX_HTTP_CREATED 201

View File

@ -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_read_request_handler(ngx_http_request_t *r);
static void ngx_http_upstream_process_header(ngx_http_request_t *r, static void ngx_http_upstream_process_header(ngx_http_request_t *r,
ngx_http_upstream_t *u); 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, static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r,
ngx_http_upstream_t *u); ngx_http_upstream_t *u);
static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r,
@ -2530,6 +2533,20 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
continue; continue;
} }
if (rc == NGX_OK
&& u->headers_in.status_n == NGX_HTTP_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; break;
} }
@ -2567,6 +2584,152 @@ 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");
}
}
if (u->reinit_request(r) != NGX_OK) {
return NGX_ERROR;
}
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 static ngx_int_t
ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
{ {

View File

@ -185,6 +185,7 @@ typedef struct {
ngx_flag_t pass_request_headers; ngx_flag_t pass_request_headers;
ngx_flag_t pass_request_body; ngx_flag_t pass_request_body;
ngx_flag_t pass_trailers; ngx_flag_t pass_trailers;
ngx_flag_t pass_early_hints;
ngx_flag_t ignore_client_abort; ngx_flag_t ignore_client_abort;
ngx_flag_t intercept_errors; ngx_flag_t intercept_errors;
@ -354,6 +355,7 @@ struct ngx_http_upstream_s {
ngx_buf_t buffer; ngx_buf_t buffer;
off_t length; off_t length;
off_t early_hints_length;
ngx_chain_t *out_bufs; ngx_chain_t *out_bufs;
ngx_chain_t *busy_bufs; ngx_chain_t *busy_bufs;

View File

@ -213,6 +213,7 @@ struct ngx_http_v2_stream_s {
ngx_pool_t *pool; ngx_pool_t *pool;
unsigned initialized:1;
unsigned waiting:1; unsigned waiting:1;
unsigned blocked:1; unsigned blocked:1;
unsigned exhausted:1; unsigned exhausted:1;

View File

@ -28,6 +28,8 @@
static ngx_int_t ngx_http_v2_header_filter(ngx_http_request_t *r); 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( 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); ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin);
@ -97,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_header_filter;
static ngx_http_output_header_filter_pt ngx_http_next_early_hints_filter;
static ngx_int_t static ngx_int_t
@ -109,7 +112,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
ngx_list_part_t *part; ngx_list_part_t *part;
ngx_table_elt_t *header; ngx_table_elt_t *header;
ngx_connection_t *fc; ngx_connection_t *fc;
ngx_http_cleanup_t *cln;
ngx_http_v2_stream_t *stream; ngx_http_v2_stream_t *stream;
ngx_http_v2_out_frame_t *frame; ngx_http_v2_out_frame_t *frame;
ngx_http_v2_connection_t *h2c; ngx_http_v2_connection_t *h2c;
@ -614,7 +616,196 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
ngx_http_v2_queue_blocked_frame(h2c, frame); 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); cln = ngx_http_cleanup_add(r, 0);
if (cln == NULL) { if (cln == NULL) {
@ -628,7 +819,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
fc->need_last_buf = 1; fc->need_last_buf = 1;
fc->need_flush_buf = 1; fc->need_flush_buf = 1;
return ngx_http_v2_filter_send(fc, stream); return NGX_OK;
} }
@ -1569,5 +1760,8 @@ ngx_http_v2_filter_init(ngx_conf_t *cf)
ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_v2_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; return NGX_OK;
} }

View File

@ -36,6 +36,7 @@ typedef struct {
static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); 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, static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r,
ngx_chain_t *in); ngx_chain_t *in);
static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r,
@ -75,6 +76,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_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; static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
@ -588,6 +590,154 @@ 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_lri(NULL, 0,
NGX_HTTP_V3_HEADER_STATUS_200,
NULL, 3);
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_lri(b->last, 0,
NGX_HTTP_V3_HEADER_STATUS_200,
NULL, 3);
b->last = ngx_sprintf(b->last, "%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;
}
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 static ngx_int_t
ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{ {
@ -845,6 +995,9 @@ ngx_http_v3_filter_init(ngx_conf_t *cf)
ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_v3_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_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_v3_body_filter; ngx_http_top_body_filter = ngx_http_v3_body_filter;