關(guān)于nginx proxy_set部分常見配置
nginx proxy_set部分常見配置
proxy_set_header Host $host; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請求頭的Host字段。$host變量將被替換為客戶端請求中的實際主機名。 proxy_set_header Connection ""; # 用途:清空要發(fā)送到代理服務(wù)器的HTTP請求頭的Connection字段。這可以避免由于Connection字段的錯誤配置而導(dǎo)致的代理連接無法正常關(guān)閉的問題。 proxy_set_header User-Agent $http_user_agent; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請求頭的User-Agent字段。$http_user_agent變量將被替換為客戶端請求中的實際User-Agent字符串。 proxy_set_header Referer $http_referer; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請求頭的Referer字段。$http_referer變量將被替換為客戶端請求中的實際Referer字符串。 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請求頭的X-Forwarded-For字段。該字段用于記錄原始客戶端的IP地址。$proxy_add_x_forwarded_for變量將會在原有X-Forwarded-For字段的基礎(chǔ)上添加一個新的IP地址。 proxy_set_header X-Real-IP $remote_addr; #用途:設(shè)置要發(fā)送到代理服務(wù)器的HTTP請求頭的X-Real-IP字段。該字段用于記錄客戶端的真實IP地址。$remote_addr變量將被替換為客戶端的真實IP地址。 proxy_set_header Accept-Encoding ""; #用途:清空要發(fā)送到代理服務(wù)器的HTTP請求頭的Accept-Encoding字段。這可以避免由于Accept-Encoding字段的錯誤配置而導(dǎo)致的代理服務(wù)器無法正確解壓縮響應(yīng)的問題。
需要注意的是:
這些指令的具體配置可能會因?qū)嶋H情況而異,例如需要設(shè)置的請求頭字段等,需要根據(jù)實際需求進行配置
記錄Nginx proxy_set_header的坑
背景
一個項目,使用nginx進行代理回源,新版本提交給測試同學(xué)后反饋說,回源Host被修改成固定的origin_upstream了。
第一反應(yīng):不應(yīng)該啊,這個版本我沒改回源Host相關(guān)的內(nèi)容啊,是不是你環(huán)境問題?哈哈。但又一想,origin_upstream這個值莫名其妙,恰好是proxy_pass配置中url出現(xiàn)的,明顯是有問題。
首先看了下代碼提交記錄,lua代碼沒有修改回源Host,排除;在回源的location中,增加了兩行配置。然后就沒有其他改動了。難道是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è)置源站長連接 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;的問題。
解決辦法:
在location中再加上如下配置,即可解決:
proxy_set_header Host $host;
原理
為什么location中的Host不會繼承server塊中配置的呢?
看下nginx文檔,如果在當(dāng)前級別中沒有配置,則繼承上級配置。
默認(rèn)情況,Host和Connection 會被重定義。
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 才會被覆蓋呢?
懷著疑問,去看proxy_set_header這個指令的實現(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 },
通過ngx_conf_set_keyval_slot這函數(shù)設(shè)置了ngx_http_proxy_loc_conf_t結(jié)構(gòu)體重的headers_source字段。
從下面實現(xiàn)可以看出,這個字段是一個kv鍵值對的數(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合并配置時,一處是在ngx_http_proxy_init_headers這個函數(shù)中。
繼續(xù)看,發(fā)現(xiàn),ngx_http_proxy_init_headers這個函數(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中沒有proxy_set_header配置,就集成上級配置 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語句對default_headers對了一次遍歷,如果headers_merged沒有相同的key值,則使用default_headers的覆蓋 // default_headers是什么?我們下面來看 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中,即請求的信息中。 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為空,會使用default_headers進行覆蓋。
入?yún)efault_headers其實是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對應(yīng)的默認(rèn)值為$proxy_host。
$proxy_host怎么獲取的呢?查看代碼可知,是通過對proxy_pass配置的url進行解析得出的。
而我們的配置文件中,$proxy_host會被解析為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,然后通過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中,會調(diào)用ngx_parse_url(cf->pool, u)對url進行解析。 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; // 完成對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; }
到這里,其實已經(jīng)很清楚了:
舊版本中,location中沒有proxy_set_header這個指令,合并配置時headers_source會繼承server中設(shè)置的Host、Connecion頭,從而不會被default_headers給覆蓋。所以,回源Host正確。
新版本中,location中新增了Connecion頭的配置,但是沒有Host頭,headers_source不會繼承server中的配置,從而導(dǎo)致Host頭被默認(rèn)頭覆蓋。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Nginx在Windows下的安裝及環(huán)境配置(將nginx作為服務(wù)運行)
這篇文章主要介紹了Nginx在Windows下的安裝及環(huán)境配置,主要是將nginx作為服務(wù)運行,需要的朋友可以參考下2018-11-11nginx禁止dedecms目錄php執(zhí)行權(quán)限
nginx禁止dedecms目錄php執(zhí)行權(quán)限,找到配置fastcgi.conf文件,一般在/usr/local/nginx/conf/下面,修改如下2014-01-01解決nginx啟動失敗(bind()?to?0.0.0.0:80?failed,An?attempt?was?
這篇文章主要介紹了解決nginx啟動失敗問題(bind()?to?0.0.0.0:80?failed,An?attempt?was?made?to?access?a?socket?in?...),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05Nginx結(jié)合keepalived實現(xiàn)雙機熱備方案
Nginx難免遇見故障,可以使用使用keepalived來實現(xiàn)Nginx的高可用,本文主要介紹了Nginx結(jié)合keepalived實現(xiàn)雙機熱備方案,具有一定的參考價值,感興趣的可以了解一下2024-05-05