關(guān)于nginx proxy_set部分常見(jiàn)配置
nginx proxy_set部分常見(jiàn)配置
proxy_set_header Host $host; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的Host字段。$host變量將被替換為客戶端請(qǐng)求中的實(shí)際主機(jī)名。 proxy_set_header Connection ""; # 用途:清空要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的Connection字段。這可以避免由于Connection字段的錯(cuò)誤配置而導(dǎo)致的代理連接無(wú)法正常關(guān)閉的問(wèn)題。 proxy_set_header User-Agent $http_user_agent; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的User-Agent字段。$http_user_agent變量將被替換為客戶端請(qǐng)求中的實(shí)際User-Agent字符串。 proxy_set_header Referer $http_referer; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的Referer字段。$http_referer變量將被替換為客戶端請(qǐng)求中的實(shí)際Referer字符串。 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的X-Forwarded-For字段。該字段用于記錄原始客戶端的IP地址。$proxy_add_x_forwarded_for變量將會(huì)在原有X-Forwarded-For字段的基礎(chǔ)上添加一個(gè)新的IP地址。 proxy_set_header X-Real-IP $remote_addr; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的X-Real-IP字段。該字段用于記錄客戶端的真實(shí)IP地址。$remote_addr變量將被替換為客戶端的真實(shí)IP地址。 proxy_set_header Accept-Encoding ""; #用途:清空要發(fā)送到代理服務(wù)器的HTTP請(qǐng)求頭的Accept-Encoding字段。這可以避免由于Accept-Encoding字段的錯(cuò)誤配置而導(dǎo)致的代理服務(wù)器無(wú)法正確解壓縮響應(yīng)的問(wèn)題。
需要注意的是:
這些指令的具體配置可能會(huì)因?qū)嶋H情況而異,例如需要設(shè)置的請(qǐng)求頭字段等,需要根據(jù)實(shí)際需求進(jìn)行配置
記錄Nginx proxy_set_header的坑
背景
一個(gè)項(xiàng)目,使用nginx進(jìn)行代理回源,新版本提交給測(cè)試同學(xué)后反饋說(shuō),回源Host被修改成固定的origin_upstream了。
第一反應(yīng):不應(yīng)該啊,這個(gè)版本我沒(méi)改回源Host相關(guān)的內(nèi)容啊,是不是你環(huán)境問(wèn)題?哈哈。但又一想,origin_upstream這個(gè)值莫名其妙,恰好是proxy_pass配置中url出現(xiàn)的,明顯是有問(wèn)題。
首先看了下代碼提交記錄,lua代碼沒(méi)有修改回源Host,排除;在回源的location中,增加了兩行配置。然后就沒(méi)有其他改動(dòng)了。難道是location中的配置導(dǎo)致的?
配置文件結(jié)構(gòu)如下:
http { ... upstream origin_upstream { ... } server { proxy_set_header Host $host; proxy_set_header Connection ""; proxy_http_version 1.1; ... location x { ... } location y { ... } location @origin{ ... # 如下兩行是新版新增的,此處不是重復(fù),是根據(jù)配置設(shè)置源站長(zhǎng)連接 proxy_set_header Connection $switch; proxy_http_version 1.1; proxy_pass http://origin_upstream$request_uri; ... } } }
去掉location中上述新增的兩行配置后,就正常了!最終確認(rèn),是proxy_set_header Connection $switch;的問(wèn)題。
解決辦法:
在location中再加上如下配置,即可解決:
proxy_set_header Host $host;
原理
為什么location中的Host不會(huì)繼承server塊中配置的呢?
看下nginx文檔,如果在當(dāng)前級(jí)別中沒(méi)有配置,則繼承上級(jí)配置。
默認(rèn)情況,Host和Connection 會(huì)被重定義。
Syntax: proxy_set_header field value; Default: proxy_set_header Host $proxy_host; proxy_set_header Connection close; Context: http, server, location Allows redefining or appending fields to the request header passed to the proxied server. The value can contain text, variables, and their combinations. These directives are inherited from the previous configuration level if and only if there are no proxy_set_header directives defined on the current level. By default, only two fields are redefined: proxy_set_header Host $proxy_host; proxy_set_header Connection close;
到底什么情況下,Host和Connection 才會(huì)被覆蓋呢?
懷著疑問(wèn),去看proxy_set_header這個(gè)指令的實(shí)現(xiàn)。
{ ngx_string("proxy_set_header"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_source), NULL },
通過(guò)ngx_conf_set_keyval_slot這函數(shù)設(shè)置了ngx_http_proxy_loc_conf_t結(jié)構(gòu)體重的headers_source字段。
從下面實(shí)現(xiàn)可以看出,這個(gè)字段是一個(gè)kv鍵值對(duì)的數(shù)組。
char * ngx_conf_set_keyval_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; ngx_str_t *value; ngx_array_t **a; ngx_keyval_t *kv; ngx_conf_post_t *post; a = (ngx_array_t **) (p + cmd->offset); if (*a == NULL) { *a = ngx_array_create(cf->pool, 4, sizeof(ngx_keyval_t)); if (*a == NULL) { return NGX_CONF_ERROR; } } kv = ngx_array_push(*a); if (kv == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; kv->key = value[1]; kv->value = value[2]; if (cmd->post) { post = cmd->post; return post->post_handler(cf, post, kv); } return NGX_CONF_OK; }
headers_source字段在什么地方使用呢?
一處是在在ngx_http_proxy_merge_loc_conf合并配置時(shí),一處是在ngx_http_proxy_init_headers這個(gè)函數(shù)中。
繼續(xù)看,發(fā)現(xiàn),ngx_http_proxy_init_headers這個(gè)函數(shù)在ngx_http_proxy_merge_loc_conf有調(diào)用。
static char * ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_proxy_loc_conf_t *prev = parent; ngx_http_proxy_loc_conf_t *conf = child; ... if (conf->headers_source == NULL) { conf->headers = prev->headers; #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif // 如果location中沒(méi)有proxy_set_header配置,就集成上級(jí)配置 conf->headers_source = prev->headers_source; } //緊接著就是調(diào)用ngx_http_proxy_init_headers rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, ngx_http_proxy_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } ... }
我們繼續(xù)看下ngx_http_proxy_init_headers的邏輯:
static ngx_int_t ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers) { u_char *p; size_t size; uintptr_t *code; ngx_uint_t i; ngx_array_t headers_names, headers_merged; ngx_keyval_t *src, *s, *h; ngx_hash_key_t *hk; ngx_hash_init_t hash; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; if (headers->hash.buckets) { return NGX_OK; } if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK) { return NGX_ERROR; } if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t)) != NGX_OK) { return NGX_ERROR; } headers->lengths = ngx_array_create(cf->pool, 64, 1); if (headers->lengths == NULL) { return NGX_ERROR; } headers->values = ngx_array_create(cf->pool, 512, 1); if (headers->values == NULL) { return NGX_ERROR; } // location中有配置proxy_set_header,則將其放入到headers_merged中。 if (conf->headers_source) { src = conf->headers_source->elts; for (i = 0; i < conf->headers_source->nelts; i++) { s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = src[i]; } } h = default_headers; // while語(yǔ)句對(duì)default_headers對(duì)了一次遍歷,如果headers_merged沒(méi)有相同的key值,則使用default_headers的覆蓋 // default_headers是什么?我們下面來(lái)看 while (h->key.len) { src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) { goto next; } } s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; } *s = *h; next: h++; } // 下面流程是將headers_merged合并如到入?yún)eaders->hash中,即請(qǐng)求的信息中。 src = headers_merged.elts; for (i = 0; i < headers_merged.nelts; i++) { hk = ngx_array_push(&headers_names); if (hk == NULL) { return NGX_ERROR; } hk->key = src[i].key; hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len); hk->value = (void *) 1; if (src[i].value.len == 0) { continue; } copy = ngx_array_push_n(headers->lengths, sizeof(ngx_http_script_copy_code_t)); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) (void *) ngx_http_script_copy_len_code; copy->len = src[i].key.len; size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(headers->values, size); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len; p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); ngx_memcpy(p, src[i].key.data, src[i].key.len); ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &src[i].value; sc.flushes = &headers->flushes; sc.lengths = &headers->lengths; sc.values = &headers->values; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; code = ngx_array_push_n(headers->values, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; hash.hash = &headers->hash; hash.key = ngx_hash_key_lc; hash.max_size = conf->headers_hash_max_size; hash.bucket_size = conf->headers_hash_bucket_size; hash.name = "proxy_headers_hash"; hash.pool = cf->pool; hash.temp_pool = NULL; return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); }
從上面可以看到,location中如果headers_source為空,會(huì)使用default_headers進(jìn)行覆蓋。
入?yún)efault_headers其實(shí)是ngx_http_proxy_headers,定義如下:
static ngx_keyval_t ngx_http_proxy_headers[] = { { ngx_string("Host"), ngx_string("$proxy_host") }, { 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 } };
我們看到Host對(duì)應(yīng)的默認(rèn)值為$proxy_host。
$proxy_host怎么獲取的呢?查看代碼可知,是通過(guò)對(duì)proxy_pass配置的url進(jìn)行解析得出的。
而我們的配置文件中,$proxy_host會(huì)被解析為origin_upstream。
proxy_pass http://origin_upstream$request_uri;
$proxy_host解析的相關(guān)代碼:
static ngx_http_variable_t ngx_http_proxy_vars[] = { { ngx_string("proxy_host"), NULL, ngx_http_proxy_host_variable, 0, NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... } //proxy_host就是ctx->vars.host_header static ngx_int_t ngx_http_proxy_host_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_http_proxy_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } v->len = ctx->vars.host_header.len; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = ctx->vars.host_header.data; return NGX_OK; } //在proxy_pass指令解析函數(shù)中,解析url,得到host,然后通過(guò)ngx_http_proxy_set_vars將host設(shè)置給host_header static char * ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_str_t *value, *url; ngx_url_t u; ... u.url.len = url->len - add; u.url.data = url->data + add; u.default_port = port; u.uri_part = 1; u.no_resolve = 1; // ngx_http_upstream_add中,會(huì)調(diào)用ngx_parse_url(cf->pool, u)對(duì)url進(jìn)行解析。 plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); if (plcf->upstream.upstream == NULL) { return NGX_CONF_ERROR; } plcf->vars.schema.len = add; plcf->vars.schema.data = url->data; plcf->vars.key_start = plcf->vars.schema; // 完成對(duì)host_header的設(shè)置 ngx_http_proxy_set_vars(&u, &plcf->vars); ... } static void ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v) { if (u->family != AF_UNIX) { if (u->no_port || u->port == u->default_port) { v->host_header = u->host; if (u->default_port == 80) { ngx_str_set(&v->port, "80"); } else { ngx_str_set(&v->port, "443"); } } else { v->host_header.len = u->host.len + 1 + u->port_text.len; v->host_header.data = u->host.data; v->port = u->port_text; } v->key_start.len += v->host_header.len; } else { ngx_str_set(&v->host_header, "localhost"); ngx_str_null(&v->port); v->key_start.len += sizeof("unix:") - 1 + u->host.len + 1; } v->uri = u->uri; }
到這里,其實(shí)已經(jīng)很清楚了:
舊版本中,location中沒(méi)有proxy_set_header這個(gè)指令,合并配置時(shí)headers_source會(huì)繼承server中設(shè)置的Host、Connecion頭,從而不會(huì)被default_headers給覆蓋。所以,回源Host正確。
新版本中,location中新增了Connecion頭的配置,但是沒(méi)有Host頭,headers_source不會(huì)繼承server中的配置,從而導(dǎo)致Host頭被默認(rèn)頭覆蓋。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Docker實(shí)現(xiàn)Nginx反向代理
本文主要介紹了使用Docker實(shí)現(xiàn)Nginx反向代理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06Nginx在Windows下的安裝及環(huán)境配置(將nginx作為服務(wù)運(yùn)行)
這篇文章主要介紹了Nginx在Windows下的安裝及環(huán)境配置,主要是將nginx作為服務(wù)運(yùn)行,需要的朋友可以參考下2018-11-11nginx禁止dedecms目錄php執(zhí)行權(quán)限
nginx禁止dedecms目錄php執(zhí)行權(quán)限,找到配置fastcgi.conf文件,一般在/usr/local/nginx/conf/下面,修改如下2014-01-01Nginx防盜鏈與服務(wù)優(yōu)化配置的全過(guò)程
由于Nginx本身的一些優(yōu)點(diǎn),輕量,開(kāi)源,易用,越來(lái)越多的公司使用nginx作為自己公司的web應(yīng)用服務(wù)器,下面這篇文章主要給大家介紹了關(guān)于Nginx防盜鏈與服務(wù)優(yōu)化配置的相關(guān)資料,需要的朋友可以參考下2022-01-01nginx高可用集群的實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了nginx高可用集群的實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10解決nginx啟動(dòng)失敗(bind()?to?0.0.0.0:80?failed,An?attempt?was?
這篇文章主要介紹了解決nginx啟動(dòng)失敗問(wèn)題(bind()?to?0.0.0.0:80?failed,An?attempt?was?made?to?access?a?socket?in?...),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Nginx結(jié)合keepalived實(shí)現(xiàn)雙機(jī)熱備方案
Nginx難免遇見(jiàn)故障,可以使用使用keepalived來(lái)實(shí)現(xiàn)Nginx的高可用,本文主要介紹了Nginx結(jié)合keepalived實(shí)現(xiàn)雙機(jī)熱備方案,具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05