nginx_process_headers

Overview

nginx provides flexible way to manipulate headers, you can do this by directive or by lua, here we only say directive, headers can be request header or response header, that means nginx gives you the way to manipulate request header before sending to upstream and response header before sending to client.
nginx supports

  • add a header
  • update existing header
  • delete a header

request header related directive before sending to upstream

  • proxy_set_header

response header related directives before sending to client

  • add_header
  • proxy_ignore_headers
  • proxy_hide_header
  • proxy_pass_header

Header building

For proxy header sent to upstream, it depends on three parts.

  • request sent(header in request itself) lowest priority
  • default behavior for some proxy headers
  • user setting by directive(proxy_set_header) highest priority

First check user setting, if has, use that value, otherwise, use default behavior for some proxy headers, if not header has no default behavior, just pass it to upstream.

For response header sent to client, it depends on two parts.

  • ignore/hide
  • user setting by directive(add_header) highest priority

First check user setting, if has, use that value, otherwise not send to client if ignored or hidden.

Data Structure

proxy header(most headers from client) sent to upstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
typedef struct {
/* as proxy headers value can have variable, so need to compile it, then saved at values(handler)
* hash is the hash table for proxy header name
*/
ngx_array_t *flushes;
ngx_array_t *lengths;
ngx_array_t *values;
ngx_hash_t hash;
} ngx_http_proxy_headers_t;

typedef struct {
ngx_str_t key;
ngx_str_t value;
} ngx_keyval_t;

typedef struct {
ngx_http_upstream_conf_t upstream;

/* headers(used runtime sent to backend) are merged result from two parts
* default proxy header : ngx_http_proxy_headers
* header set by proxy_set_header directive : headers_source(high priority)
*/
ngx_http_proxy_headers_t headers;

/* headers set by proxy_set_header directive, user defines
* proxy_set_header supports using variable as value like this:
* proxy_set_header Host $http_host;
*/
ngx_array_t *headers_source; // ngx_keyval_t array
};

/*
* default proxy headers(overwrite such header if client sends it as well)
* these headers will be sent/removed when sending request to backend
*
* like { ngx_string("TE"), ngx_string("")} means remove such header
* when sending request to backend, even client sends it.
*
* these can be overwritten by proxy_set_header
* because we merge these two parts during conf
* but proxy_set_header has higher priority
*/
static ngx_keyval_t ngx_http_proxy_headers[] = {
{ ngx_string("Host"), ngx_string("$proxy_host") },
/* if no set default Connection header is set with Connection: close when send request to upstream */
{ ngx_string("Connection"), ngx_string("close") },
{ ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
{ ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") },
{ ngx_string("TE"), ngx_string("") },
{ ngx_string("Keep-Alive"), ngx_string("") },
{ ngx_string("Expect"), ngx_string("") },
{ ngx_string("Upgrade"), ngx_string("") },
{ ngx_null_string, ngx_null_string }
};

response header sent to client(hide or ignore some headers)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// response header sent to client(hide or ignore some headers)
typedef struct {
/* ignore header bit for supported ignore header, user can only ignore these headers
* you can ignore processing these response headers(limit scope) from backend
* no set var etc

* ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[] = {
* { ngx_string("X-Accel-Redirect"), NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT },
* { ngx_string("X-Accel-Expires"), NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES },
* { ngx_string("X-Accel-Limit-Rate"), NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE },
* { ngx_string("X-Accel-Buffering"), NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING },
* { ngx_string("X-Accel-Charset"), NGX_HTTP_UPSTREAM_IGN_XA_CHARSET },
* { ngx_string("Expires"), NGX_HTTP_UPSTREAM_IGN_EXPIRES },
* { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL },
* { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE },
* { ngx_string("Vary"), NGX_HTTP_UPSTREAM_IGN_VARY },
* { ngx_null_string, 0 }
* };

* by default, no header is ignored
*/
ngx_uint_t ignore_headers;

/* hide_headers for quick searching */
ngx_hash_t hide_headers_hash;

/* by default nginx will hide some headers when send response to client
* these headers like Server, you can also add more hide header by proxy_hide_header
*
* if you don't hide some default headers you can use
* proxy_pass_header to exclude them from hide_headers
*/

/* both ignore and hiding headers are not processed, so you can't get that header value by ngx.header.xx
* but ignore header has limit group, only support some headers
* hide header can hide any header, that's the difference
/
ngx_array_t *hide_headers;
ngx_array_t *pass_headers;
} ngx_http_upstream_conf_t;

static ngx_str_t ngx_http_proxy_hide_headers[] = {
/* by default, we did NOT send these to client, but you can use proxy_pass_header to exclude a header
* let nginx sends it to client
*/
ngx_string("Date"), // exception: Date means NOT use Date header from backend, but use nginx cache time, still send Date to client!!!
ngx_string("Server"),
ngx_string("X-Pad"),
ngx_string("X-Accel-Expires"),
ngx_string("X-Accel-Redirect"),
ngx_string("X-Accel-Limit-Rate"),
ngx_string("X-Accel-Buffering"),
ngx_string("X-Accel-Charset"),
ngx_null_string
};

typedef struct {
// set by add_header directive(user setting response header)
ngx_array_t *headers;
} ngx_http_headers_conf_t;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// each request has such instance
struct ngx_http_upstream_s {
/* all parsed headers from backend */
ngx_http_upstream_headers_in_t headers_in;
};

struct ngx_http_request_s {
ngx_http_upstream_t *upstream;

ngx_http_headers_in_t headers_in; // request headers from client
ngx_http_headers_out_t headers_out; // response header sent to client
}

typedef struct {
ngx_list_t headers;
ngx_list_t trailers;

ngx_uint_t status;
ngx_str_t status_line;

ngx_table_elt_t *server;
ngx_table_elt_t *date;
ngx_table_elt_t *content_length;
ngx_table_elt_t *content_encoding;
ngx_table_elt_t *location;
ngx_table_elt_t *refresh;
ngx_table_elt_t *last_modified;
ngx_table_elt_t *content_range;
ngx_table_elt_t *accept_ranges;
ngx_table_elt_t *www_authenticate;
ngx_table_elt_t *expires;
ngx_table_elt_t *etag;

ngx_str_t *override_charset;

size_t content_type_len;
ngx_str_t content_type;
ngx_str_t charset;
u_char *content_type_lowcase;
ngx_uint_t content_type_hash;

ngx_array_t cache_control;
ngx_array_t link;

off_t content_length_n;
off_t content_offset;
time_t date_time;
time_t last_modified_time;
} ngx_http_headers_out_t;

typedef struct {
/* all headers parsed, each is a ngx_table_elt_t
* below header pointer points to the element in
* the list
*/
ngx_list_t headers;

/* shortcut for HOST header in headers array
* the ngx_table_elt_t->value points to r->buf
* NOT copy from r->buf!!!
*/
ngx_table_elt_t *host;
ngx_table_elt_t *connection;
ngx_table_elt_t *if_modified_since;
ngx_table_elt_t *if_unmodified_since;
ngx_table_elt_t *if_match;
ngx_table_elt_t *if_none_match;
ngx_table_elt_t *user_agent;
ngx_table_elt_t *referer;
ngx_table_elt_t *content_length;
ngx_table_elt_t *content_range;
ngx_table_elt_t *content_type;

ngx_table_elt_t *range;
ngx_table_elt_t *if_range;

ngx_table_elt_t *transfer_encoding;
ngx_table_elt_t *te;
ngx_table_elt_t *expect;
ngx_table_elt_t *upgrade;

#if (NGX_HTTP_GZIP || NGX_HTTP_HEADERS)
ngx_table_elt_t *accept_encoding;
ngx_table_elt_t *via;
#endif

ngx_table_elt_t *authorization;

ngx_table_elt_t *keep_alive;
#if (NGX_HTTP_X_FORWARDED_FOR)
ngx_array_t x_forwarded_for;
#endif

#if (NGX_HTTP_REALIP)
ngx_table_elt_t *x_real_ip;
#endif

#if (NGX_HTTP_HEADERS)
ngx_table_elt_t *accept;
ngx_table_elt_t *accept_language;
#endif

#if (NGX_HTTP_DAV)
ngx_table_elt_t *depth;
ngx_table_elt_t *destination;
ngx_table_elt_t *overwrite;
ngx_table_elt_t *date;
#endif
} ngx_http_headers_in_t;

API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// build r->headers_in from request header
static void ngx_http_process_request_headers(ngx_event_t *rev);


//build header sent to upstream(backend), create a temp buffer to hold all header copied from r->headers_in
static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r);
// build header sent to client(set r->headers_out) when parsed all response headers
static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u);


// header filter to add/update/remove(manipulate r->headers_out) response header
static ngx_int_t ngx_http_headers_filter(ngx_http_request_t *r);
// last header filter based on r->headers_out create a temp buffer to hold all header copied from r->headers_out, then send it out
static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r)