8000 HTTP Early Hints. · nginx/nginx@04da1fa · GitHub
[go: up one dir, main page]

Skip to content

Commit 04da1fa

Browse files
committed
HTTP Early Hints.
The change implements parsing Early Hints response from the proxied server and forwarding it to the client. Also, an Early Hints response could be sent to the client by the upstream module before choosing a proxied server. This happens if there are headers to send, which can be configured or added by third-party modules using the early hint filters. A new parameter "early" is added to the "add_header" directive. The parameter instructs nginx to send this header as part of Early Hints response. The header is also sent in the main response. Also, a new directive "early_hints" is added, which accepts a predicate to enable Early Hints. Example: add_header X-Foo foo early; early_hints $http2 $http3; proxy_pass http://bar.example.com;
1 parent e7bd255 commit 04da1fa

10 files changed

+653
-4
lines changed

src/http/modules/ngx_http_headers_filter_module.c

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ struct ngx_http_header_val_s {
2828
ngx_str_t key;
2929
ngx_http_set_header_pt handler;
3030
ngx_uint_t offset;
31-
ngx_uint_t always; /* unsigned always:1 */
31+
unsigned always:1;
32+
unsigned early:1;
3233
};
3334

3435

@@ -159,6 +160,7 @@ ngx_module_t ngx_http_headers_filter_module = {
159160

160161

161162
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
163+
static ngx_http_output_header_filter_pt ngx_http_next_early_hints_filter;
162164
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
163165

164166

@@ -244,6 +246,42 @@ ngx_http_headers_filter(ngx_http_request_t *r)
244246
}
245247

246248

249+
static ngx_int_t
250+
ngx_http_early_hints_filter(ngx_http_request_t *r)
251+
{
252+
ngx_str_t value;
253+
ngx_uint_t i;
254+
ngx_http_header_val_t *h;
255+
ngx_http_headers_conf_t *conf;
256+
257+
if (r != r->main) {
258+
return ngx_http_next_early_hints_filter(r);
259+
}
260+
261+
conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
262+
263+
if (conf->headers) {
264+
h = conf->headers->elts;
265+
for (i = 0; i < conf->headers->nelts; i++) {
266+
267+
if (!h[i].early) {
268+
continue;
269+
}
270+
271+
if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
272+
return NGX_ERROR;
273+
}
274+
275+
if (h[i].handler(r, &h[i], &value) != NGX_OK) {
276+
return NGX_ERROR;
277+
}
278+
}
279+
}
280+
281+
return ngx_http_next_early_hints_filter(r);
282+
}
283+
284+
247285
static ngx_int_t
248286
ngx_http_trailers_filter(ngx_http_request_t *r, ngx_chain_t *in)
249287
{
@@ -696,6 +734,9 @@ ngx_http_headers_filter_init(ngx_conf_t *cf)
696734
ngx_http_next_header_filter = ngx_http_top_header_filter;
697735
ngx_http_top_header_filter = ngx_http_headers_filter;
698736

737+
ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter;
738+
ngx_http_top_early_hints_filter = ngx_http_early_hints_filter;
739+
699740
ngx_http_next_body_filter = ngx_http_top_body_filter;
700741
ngx_http_top_body_filter = ngx_http_trailers_filter;
701742

@@ -804,6 +845,7 @@ ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
804845
hv->handler = NULL;
805846
hv->offset = 0;
806847
hv->always = 0;
848+
hv->early = 0;
807849

808850
if (headers == &hcf->headers) {
809851
hv->handler = ngx_http_add_header;
@@ -840,13 +882,17 @@ ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
840882
return NGX_CONF_OK;
841883
}
842884

843-
if (ngx_strcmp(value[3].data, "always") != 0) {
885+
if (ngx_strcmp(value[3].data, "always") == 0) {
886+
hv->always = 1;
887+
888+
} else if (ngx_strcmp(value[3].data, "early") == 0) {
889+
hv->early = 1;
890+
891+
} else {
844892
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
845893
"invalid parameter \"%V\"", &value[3]);
846894
return NGX_CONF_ERROR;
847895
}
848896

849-
hv->always = 1;
850-
851897
return NGX_CONF_OK;
852898
}

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ extern ngx_str_t ngx_http_html_default_types[];
191191

192192

193193
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
194+
extern ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter;
194195
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
195196
extern ngx_http_request_body_filter_pt ngx_http_top_request_body_filter;
196197

src/http/ngx_http_core_module.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,13 @@ static ngx_command_t ngx_http_core_commands[] = {
663663
offsetof(ngx_http_core_loc_conf_t, etag),
664664
NULL },
665665

666+
{ ngx_string("early_hints"),
667+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
668+
ngx_http_set_predicate_slot,
669+
NGX_HTTP_LOC_CONF_OFFSET,
670+
offsetof(ngx_http_core_loc_conf_t, early_hints),
671+
NULL },
672+
666673
{ ngx_string("error_page"),
667674
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
668675
|NGX_CONF_2MORE,
@@ -1841,6 +1848,10 @@ ngx_http_send_header(ngx_http_request_t *r)
18411848
return NGX_ERROR;
18421849
}
18431850

1851+
if (r->headers_out.status == NGX_HTTP_EARLY_HINTS) {
1852+
return ngx_http_top_early_hints_filter(r);
1853+
}
1854+
18441855
if (r->err_status) {
18451856
r->headers_out.status = r->err_status;
18461857
r->headers_out.status_line.len = 0;
@@ -3622,6 +3633,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf)
36223633
clcf->recursive_error_pages = NGX_CONF_UNSET;
36233634
clcf->chunked_transfer_encoding = NGX_CONF_UNSET;
36243635
clcf->etag = NGX_CONF_UNSET;
3636+
clcf->early_hints = NGX_CONF_UNSET_PTR;
36253637
clcf->server_tokens = NGX_CONF_UNSET_UINT;
36263638
clcf->types_hash_max_size = NGX_CONF_UNSET_UINT;
36273639
clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT;
@@ -3901,6 +3913,8 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
39013913
ngx_conf_merge_uint_value(conf->server_tokens, prev->server_tokens,
39023914
NGX_HTTP_SERVER_TOKENS_ON);
39033915

3916+
ngx_conf_merge_ptr_value(conf->early_hints, prev->early_hints, NULL);
3917+
39043918
ngx_conf_merge_ptr_value(conf->open_file_cache,
39053919
prev->open_file_cache, NULL);
39063920

src/http/ngx_http_core_module.h

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

432+
ngx_array_t *early_hints; /* early_hints */
433+
432434
ngx_array_t *error_pages; /* error_page */
433435

434436
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
@@ -12,6 +12,7 @@
1212

1313

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

1718

@@ -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("103 Early Hints");
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->header_sent || r != r->main) {
643+
return NGX_OK;
644+
}
645+
646+
len = 0;
647+
648+
part = &r->headers_out.headers.part;
649+
header = part->elts;
650+
651+
for (i = 0; /* void */; i++) {
652+
653+
if (i >= part->nelts) {
654+
if (part->next == NULL) {
655+
break;
656+
}
657+
658+
part = part->next;
659+
header = part->elts;
660+
i = 0;
661+
}
662+
663+
if (header[i].hash == 0) {
664+
continue;
665+
}
666+
667+
len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
668+
+ sizeof(CRLF) - 1;
669+
}
670+
671+
if (len == 0) {
672+
return NGX_OK;
673+
}
674+
675+
len += sizeof("HTTP/1.x ") - 1
676+
+ ngx_http_early_hints_status_line.len + sizeof(CRLF) - 1
677+
/* the end of the header */
678+
+ sizeof(CRLF) - 1;
679+
680+
b = ngx_create_temp_buf(r->pool, len);
681+
if (b == NULL) {
682+
return NGX_ERROR;
683+
}
684+
685+
/* "HTTP/1.x" and status line */
686+
b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
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+
*b->last++ = CR; *b->last++ = LF;
692+
693+
part = &r->headers_out.headers.part;
694+
header = part->elts;
695+
696+
for (i = 0; /* void */; i++) {
697+
698+
if (i >= part->nelts) {
699+
if (part->next == NULL) {
700+
break;
701+
}
702+
703+
part = part->next;
704+
header = part->elts;
705+
i = 0;
706+
}
707+
708+
if (header[i].hash == 0) {
709+
continue;
710+
}
711+
712+
b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
713+
*b->last++ = ':'; *b->last++ = ' ';
714+
715+
b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
716+
*b->last++ = CR; *b->last++ = LF;
717+
}
718+
719+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
720+
"%*s", (size_t) (b->last - b->pos), b->pos);
721+
722+
/* the end of HTTP early hints */
723+
*b->last++ = CR; *b->last++ = LF;
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)
0