Skip to content

Commit 662c1dd

Browse files
committed
Upstream: early hints support.
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;
1 parent ea001fe commit 662c1dd

13 files changed

+698
-10
lines changed

src/http/modules/ngx_http_grpc_module.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1869,7 +1869,8 @@ ngx_http_grpc_process_header(ngx_http_request_t *r)
18691869
return NGX_HTTP_UPSTREAM_INVALID_HEADER;
18701870
}
18711871

1872-
if (status < NGX_HTTP_OK) {
1872+
if (status < NGX_HTTP_OK && status != NGX_HTTP_EARLY_HINTS)
1873+
{
18731874
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
18741875
"upstream sent unexpected :status \"%V\"",
18751876
status_line);
@@ -1902,6 +1903,10 @@ ngx_http_grpc_process_header(ngx_http_request_t *r)
19021903
h->lowcase_key = h->key.data;
19031904
h->hash = ngx_hash_key(h->key.data, h->key.len);
19041905

1906+
if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
1907+
continue;
1908+
}
1909+
19051910
hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
19061911
h->lowcase_key, h->key.len);
19071912

@@ -4413,6 +4418,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf)
44134418
conf->upstream.pass_request_body = 1;
44144419
conf->upstream.force_ranges = 0;
44154420
conf->upstream.pass_trailers = 1;
4421+
conf->upstream.pass_early_hints = 1;
44164422
conf->upstream.preserve_output = 1;
44174423

44184424
conf->headers_source = NGX_CONF_UNSET_PTR;

src/http/modules/ngx_http_proxy_module.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,13 @@ ngx_http_proxy_process_status_line(ngx_http_request_t *r)
18881888
u->headers_in.status_n, &u->headers_in.status_line);
18891889

18901890
if (ctx->status.http_version < NGX_HTTP_VERSION_11) {
1891+
1892+
if (ctx->status.code == NGX_HTTP_EARLY_HINTS) {
1893+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
1894+
"upstream sent HTTP/1.0 response with early hints");
1895+
return NGX_HTTP_UPSTREAM_INVALID_HEADER;
1896+
}
1897+
18911898
u->headers_in.connection_close = 1;
18921899
}
18931900

@@ -1949,6 +1956,14 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
19491956
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
19501957
}
19511958

1959+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1960+
"http proxy header: \"%V: %V\"",
1961+
&h->key, &h->value);
1962+
1963+
if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
1964+
continue;
1965+
}
1966+
19521967
hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
19531968
h->lowcase_key, h->key.len);
19541969

@@ -1960,10 +1975,6 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
19601975
}
19611976
}
19621977

1963-
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1964-
"http proxy header: \"%V: %V\"",
1965-
&h->key, &h->value);
1966-
19671978
continue;
19681979
}
19691980

@@ -1974,6 +1985,10 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
19741985
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
19751986
"http proxy header done");
19761987

1988+
if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
1989+
return NGX_OK;
1990+
}
1991+
19771992
/*
19781993
* if no "Server" and "Date" in header line,
19791994
* then add the special empty headers
@@ -3628,10 +3643,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
36283643
conf->ssl_conf_commands = NGX_CONF_UNSET_PTR;
36293644
#endif
36303645

3631-
/* "proxy_cyclic_temp_file" is disabled */
3646+
/* the hardcoded values */
36323647
conf->upstream.cyclic_temp_file = 0;
3633-
36343648
conf->upstream.change_buffering = 1;
3649+
conf->upstream.pass_early_hints = 1;
36353650

36363651
conf->headers_source = NGX_CONF_UNSET_PTR;
36373652

src/http/ngx_http.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ ngx_uint_t ngx_http_max_module;
7272

7373

7474
ngx_http_output_header_filter_pt ngx_http_top_header_filter;
75+
ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter;
7576
ngx_http_output_body_filter_pt ngx_http_top_body_filter;
7677
ngx_http_request_body_filter_pt ngx_http_top_request_body_filter;
7778

src/http/ngx_http.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r,
152152
ngx_int_t ngx_http_read_unbuffered_request_body(ngx_http_request_t *r);
153153

154154
ngx_int_t ngx_http_send_header(ngx_http_request_t *r);
155+
ngx_int_t ngx_http_send_early_hints(ngx_http_request_t *r);
155156
ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r,
156157
ngx_int_t error);
157158
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[];
191192

192193

193194
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
195+
extern ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter;
194196
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
195197
extern ngx_http_request_body_filter_pt ngx_http_top_request_body_filter;
196198

src/http/ngx_http_core_module.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,13 @@ static ngx_command_t ngx_http_core_commands[] = {
670670
offsetof(ngx_http_core_loc_conf_t, etag),
671671
NULL },
672672

673+
{ ngx_string("early_hints"),
674+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
675+
ngx_http_set_predicate_slot,
676+
NGX_HTTP_LOC_CONF_OFFSET,
677+
offsetof(ngx_http_core_loc_conf_t, early_hints),
678+
NULL },
679+
673680
{ ngx_string("error_page"),
674681
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
675682
|NGX_CONF_2MORE,
@@ -1857,6 +1864,37 @@ ngx_http_send_header(ngx_http_request_t *r)
18571864
}
18581865

18591866

1867+
ngx_int_t
1868+
ngx_http_send_early_hints(ngx_http_request_t *r)
1869+
{
1870+
ngx_int_t rc;
1871+
ngx_http_core_loc_conf_t *clcf;
1872+
1873+
if (r->post_action) {
1874+
return NGX_OK;
1875+
}
1876+
1877+
if (r->header_sent) {
1878+
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
1879+
"header already sent");
1880+
return NGX_ERROR;
1881+
}
1882+
1883+
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
1884+
1885+
rc = ngx_http_test_predicates(r, clcf->early_hints);
1886+
1887+
if (rc != NGX_DECLINED) {
1888+
return rc;
1889+
}
1890+
1891+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1892+
"http send early hints \"%V?%V\"", &r->uri, &r->args);
1893+
1894+
return ngx_http_top_early_hints_filter(r);
1895+
}
1896+
1897+
18601898
ngx_int_t
18611899
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
18621900
{
@@ -3637,6 +3675,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf)
36373675
clcf->chunked_transfer_encoding = NGX_CONF_UNSET;
36383676
clcf->etag = NGX_CONF_UNSET;
36393677
clcf->server_tokens = NGX_CONF_UNSET_UINT;
3678+
clcf->early_hints = NGX_CONF_UNSET_PTR;
36403679
clcf->types_hash_max_size = NGX_CONF_UNSET_UINT;
36413680
clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT;
36423681

@@ -3917,6 +3956,8 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
39173956
ngx_conf_merge_uint_value(conf->server_tokens, prev->server_tokens,
39183957
NGX_HTTP_SERVER_TOKENS_ON);
39193958

3959+
ngx_conf_merge_ptr_value(conf->early_hints, prev->early_hints, NULL);
3960+
39203961
ngx_conf_merge_ptr_value(conf->open_file_cache,
39213962
prev->open_file_cache, NULL);
39223963

src/http/ngx_http_core_module.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,8 @@ struct ngx_http_core_loc_conf_s {
430430
ngx_http_complex_value_t *disable_symlinks_from;
431431
#endif
432432

433+
ngx_array_t *early_hints; /* early_hints */
434+
433435
ngx_array_t *error_pages; /* error_page */
434436

435437
ngx_path_t *client_body_temp_path; /* client_body_temp_path */

src/http/ngx_http_header_filter_module.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf);
1515
static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r);
16+
static ngx_int_t ngx_http_early_hints_filter(ngx_http_request_t *r);
1617

1718

1819
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;
5051
static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
5152
static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF;
5253

54+
static ngx_str_t ngx_http_early_hints_status_line =
55+
ngx_string("HTTP/1.1 103 Early Hints" CRLF);
56+
5357

5458
static ngx_str_t ngx_http_status_lines[] = {
5559

@@ -625,10 +629,113 @@ ngx_http_header_filter(ngx_http_request_t *r)
625629
}
626630

627631

632+
static ngx_int_t
633+
ngx_http_early_hints_filter(ngx_http_request_t *r)
634+
{
635+
size_t len;
636+
ngx_buf_t *b;
637+
ngx_uint_t i;
638+
ngx_chain_t out;
639+
ngx_list_part_t *part;
640+
ngx_table_elt_t *header;
641+
642+
if (r != r->main) {
643+
return NGX_OK;
644+
}
645+
646+
if (r->http_version < NGX_HTTP_VERSION_11) {
647+
return NGX_OK;
648+
}
649+
650+
len = 0;
651+
652+
part = &r->headers_out.headers.part;
653+
header = part->elts;
654+
655+
for (i = 0; /* void */; i++) {
656+
657+
if (i >= part->nelts) {
658+
if (part->next == NULL) {
659+
break;
660+
}
661+
662+
part = part->next;
663+
header = part->elts;
664+
i = 0;
665+
}
666+
667+
if (header[i].hash == 0) {
668+
continue;
669+
}
670+
671+
len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
672+
+ sizeof(CRLF) - 1;
673+
}
674+
675+
if (len == 0) {
676+
return NGX_OK;
677+
}
678+
679+
len += ngx_http_early_hints_status_line.len
680+
/* the end of the early hints */
681+
+ sizeof(CRLF) - 1;
682+
683+
b = ngx_create_temp_buf(r->pool, len);
684+
if (b == NULL) {
685+
return NGX_ERROR;
686+
}
687+
688+
b->last = ngx_copy(b->last, ngx_http_early_hints_status_line.data,
689+
ngx_http_early_hints_status_line.len);
690+
691+
part = &r->headers_out.headers.part;
692+
header = part->elts;
693+
694+
for (i = 0; /* void */; i++) {
695+
696+
if (i >= part->nelts) {
697+
if (part->next == NULL) {
698+
break;
699+
}
700+
701+
part = part->next;
702+
header = part->elts;
703+
i = 0;
704+
}
705+
706+
if (header[i].hash == 0) {
707+
continue;
708+
}
709+
710+
b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
711+
*b->last++ = ':'; *b->last++ = ' ';
712+
713+
b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
714+
*b->last++ = CR; *b->last++ = LF;
715+
}
716+
717+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
718+
"%*s", (size_t) (b->last - b->pos), b->pos);
719+
720+
/* the end of HTTP early hints */
721+
*b->last++ = CR; *b->last++ = LF;
722+
723+
r->header_size = b->last - b->pos;
724+
725+
b->flush = 1;
726+
727+
out.buf = b;
728+
out.next = NULL;
729+
730+
return ngx_http_write_filter(r, &out);
731+
}
732+
733+
628734
static ngx_int_t
629735
ngx_http_header_filter_init(ngx_conf_t *cf)
630736
{
631737
ngx_http_top_header_filter = ngx_http_header_filter;
738+
ngx_http_top_early_hints_filter = ngx_http_early_hints_filter;
632739

633740
return NGX_OK;
634741
}

src/http/ngx_http_request.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
#define NGX_HTTP_CONTINUE 100
7575
#define NGX_HTTP_SWITCHING_PROTOCOLS 101
7676
#define NGX_HTTP_PROCESSING 102
77+
#define NGX_HTTP_EARLY_HINTS 103
7778

7879
#define NGX_HTTP_OK 200
7980
#define NGX_HTTP_CREATED 201

0 commit comments

Comments
 (0)