Nginx反向代理中proxy_set_header參數說明

 

Nginx proxy_set_header:即容許從新定義或添加字段傳遞給代理服務器的請求頭。該值能夠包含文本、變量和它們的組合。在沒有定義proxy_set_header時會繼承以前定義的值。默認狀況下,只有兩個字段被重定義:html

proxy_set_header Host $proxy_host;
proxy_set_header Connection close;

若是啓用緩存,來自以前請求的頭字段「If-Modified-Since」, 「If-Unmodified-Since」, 「If-None-Match」, 「If-Match」, 「Range」, 和 「If-Range」 將不會被代理服務器傳遞。
一個不會變化的「Host」頭請求字段可經過以下方式被傳遞:nginx

proxy_set_header Host       $http_host;

而後,當字段不在請求頭中就沒法傳遞了,在這種狀況下,可經過設置Host變量,將需傳遞值賦給Host變量web

proxy_set_header Host       $host;

此外,服務器名稱和端口一塊兒經過代理服務器傳遞windows

proxy_set_header Host       $host:$proxy_port;

若是請求頭的存在空的字段將不會經過代理服務器傳遞出去後端

proxy_set_header Accept-Encoding "";

簡而言之,proxy_set_header 就是可設置請求頭-並將頭信息傳遞到服務器端,不屬於請求頭的參數中也須要傳遞時,重定義下便可!緩存

================================接下來看下測試案例=========================tomcat

1)以下測試,不設置 proxy_set_header

Nginx 配置:
upstream test {
        server 192.168.1.123:9099;
        server 192.168.1.123:58080;
    }
    server {
        listen    5800;
        server_name  192.168.1.123;
        root         /usr/share/nginx/html;
        include /etc/nginx/default.d/*.conf;
        location / {
            proxy_pass http://test;
        }


測試jsp 想獲取客戶端IP、客戶端port、代理服務器IP、代理服務器port
<%@page contentType="text/html; charset=UTF-8" trimDirectiveWhitespaces="true"%>
<%
     String scheme = request.getScheme();
     String serverName = request.getServerName();
     String remoteName = request.getRemoteAddr();
     String realIP = request.getHeader("X-Forwarded-For");
     String realIP2 = request.getHeader("X-Real-IP");
     String Host = request.getHeader("Host");
     int port = request.getServerPort();
     int portR = request.getRemotePort();
     String requestURIC1 = scheme+"://"+realIP+":"+portR;
     String requestURIC2 = scheme+"://"+realIP2+":"+portR;
     String requestURIC3 = scheme+"://"+remoteName+":"+portR;
     String requestURI = scheme+"://"+serverName+":"+port;
%>  

其中:
客戶端地址1:<%=requestURIC1 %>
客戶端地址2:<%=requestURIC2 %>
客戶端地址3:<%=requestURIC3%>
服務器地址1:<%=requestURI%>
服務器地址2:<%=Host%>

測試結果
客戶端地址1:http://null:58828
客戶端地址2:http://null:58828
客戶端地址3:http://192.168.1.123:58828
服務器地址1:http://test:80
服務器地址2:test

Nginx日誌
192.168.1.177 -20508---5800 [25/Aug/2016:16:34:13 +0800] "GET /docs/test.jsp HTTP/1.1" 200 223 "

其中客戶端IP不能獲取到,而經過request.getRemoteAddr();
獲取的IP是代理服務器IP,而不是客戶端IP,而在nginx中$remote_addr變量的值是客戶端的IP,可見remoteaddr沒有傳遞。
而server_port值也不對,當前值爲5800,當前打印出的是80。
而當前代理爲http://test 全部經過host獲得的是test。
客戶端port也獲取不到值爲20508,可傳給應用的是58828

----------------------------------------------------------------------------------------------------------------

2)以下測試,設置 proxy_set_header

Nginx 配置:
upstream test {
        server 192.168.1.123:9099;
        server 192.168.1.123:58080;
    }
    server {
        listen    5800;
        server_name  192.168.1.123;
        root         /usr/share/nginx/html;
        include /etc/nginx/default.d/*.conf;
        location / {
            proxy_pass http://test;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }


測試頁面改爲:
<%@page contentType="text/html; charset=UTF-8" trimDirectiveWhitespaces="true"%>
<%
     String scheme = request.getScheme();
     String serverName = request.getServerName();
         String remoteName = request.getRemoteAddr();
         String realIP = request.getHeader("X-Forwarded-For");
         String realIP2 = request.getHeader("X-Real-IP");
         String Host = request.getHeader("Host");
         int port = request.getServerPort();
         int portR = request.getRemotePort();
         String portR2 = request.getHeader("X-Real-Port");
         String requestURIC1 = scheme+"://"+realIP+":"+portR;
         String requestURIC2 = scheme+"://"+realIP2+":"+portR;
         String requestURIC3 = scheme+"://"+remoteName+":"+portR;
         String requestURI = scheme+"://"+serverName+":"+port;
%>

其中:
客戶端地址1:<%=requestURIC1 %>
客戶端地址2:<%=requestURIC2 %>
客戶端地址3:<%=requestURIC3%>
服務器地址1:<%=requestURI%>
服務器地址2:<%=Host%>
客戶端port2:<%=portR2%>


客戶端地址1:http://192.168.1.177:21548
客戶端地址2:http://192.168.1.177:21548
客戶端地址3:http://192.168.1.123:21548
服務器地址1:http://192.168.1.123:5800
服務器地址2:192.168.1.123:5800
客戶端port2:20604

nginx日誌:

192.168.1.177 -20604---5800 [25/Aug/2016:16:38:42 +0800] "GET /docs/test.jsp HTTP/1.1" 200 275 "-" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36" "-"

除request.getRemoteAddr();獲取的值不對外,其餘值都是對的。
getRemoteAddr獲取的是代理的請求地址。
因重定義了host,因此test值被改寫成代理服務器IP。
因重定義了 X-Real-PORT-並傳遞$remote_port,客戶端port也獲取正確了。

======================proxy_set_header自定義header頭無效的問題========================bash

nginx反向代理中常常碰過的一個"坑":proxy_set_header自定義header頭無效的問題 

解決辦法:
nginx underscores_in_headers默認off 
能夠用減號-替代下劃線符號_,避免這種變態問題。nginx默認忽略掉下劃線可能有些緣由。


upstream os-8080 {
      ip_hash;
      server 192.168.1.20:8080 max_fails=3 fail_timeout=15s;
      server 192.168.1.21:8080 max_fails=3 fail_timeout=15s;
}

server {
      listen      80;
      server_name bpm.wangshibo.com;
    
      access_log  /data/nginx/logs/bpm.wangshibo.com-access.log main;
      error_log  /data/nginx/logs/bpm.wangshibo.com-error.log;
    
 nginx underscores_in_headers on;

 location / {
         proxy_pass http://os-8080;
         proxy_redirect off ;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header REMOTE-HOST $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_connect_timeout 300;
         proxy_send_timeout 300;
         proxy_read_timeout 600;
         proxy_buffer_size 512k;
         proxy_buffers 8 512k;
         proxy_busy_buffers_size 512k;
         proxy_temp_file_write_size 512k;
         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_404;
         proxy_max_temp_file_size 128m;
        } 
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        } 
}

================proxy_set_header中$proxy_host,$host,$http_host的區別================服務器

在使用Nginx作反向代理的時候,proxy_set_header功能能夠設置反向代理後的http header中的host,$http_host,$proxy_host,那麼這幾個有什麼區別呢? 

Nginx的官網文檔中說下面這兩條是作反代時默認的,因此$proxy_host 天然是 proxy_pass後面跟着的host了
proxy_set_header Host       $proxy_host;
proxy_set_header Connection close;

若是客戶端發過來的請求的header中有’HOST’這個字段時, 
$http_host和$host都是原始的’HOST’字段 
好比請求的時候HOST的值是www.csdn.net 那麼反代後仍是www.csdn.net

若是客戶端發過來的請求的header中沒有有’HOST’這個字段時, 
建議使用$host,這表示請求中的server name。

==================不妨看一個proxy_set_header配置實例==================jsp

windows客戶端(請求web服務):192.168.1.1
nginx做爲反向代理服務器:192.168.1.136
nginx做爲後端web服務器:192.168.1.137

前提條件:配置nginx轉發到後端服務器

server {
     listen 8080;
     server_name 192.168.1.136;
     
     location / {
     root "/www/html";
     index index.html;
     #auth_basic "required auth";
     #auth_basic_user_file "/usr/local/nginx/users/.htpasswd";
     error_page 404 /404.html;
}

location /images/ {
     root "/www";
     rewrite ^/images/bbs/(.*\.jpeg)$ /images/$1 break;
     rewrite ^/images/www/(.*)$ http://192.168.1.136/$1 redirect;
}

location /basic_status {
     stub_status;
}

location ^~/proxy_path/ {
     root "/www/html";
     index index.html;
     proxy_pass http://192.168.1.137/;
     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     #proxy_set_header X-Forwarded-For $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location ^~/proxy_path/ {
     root "/www/html";
     index index.html;
     proxy_pass http://192.168.1.137/;
}
}

將左側匹配到的/proxy_path/開頭的url所有轉發到後端服務器192.168.223.137

下面將一一測試各個proxy_set_header設置的變量的內容:

1)proxy_set_header Host $host;

將136代理服務器,137後端服務器的log_format修改成以下:
log_format main '$remote_addr - $remote_user [$time_local] "$request" $http_host '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

proxy_set_header Host $host;  這裏的Host變量的值對應的就是日誌中的$http_host 的值

當windows用戶訪問http://192.168.1.136:8080/proxy_path/index.html時
查看代理服務器和後端服務器的地址,能夠發現$http_host對應的值爲192.168.1.136:8080
192.168.1.1 - - [18/Jul/2017:10:21:25 +0800] "GET /favicon.ico HTTP/1.1" 192.168.1.136:8080 404 24 "http://192.168.1.136:8080/proxy_path/index.html" 
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"

若是將後端服務器關閉了,則會出現502網管錯誤:

而後開啓137後端nginx,查看日誌:
192.168.1.136 "192.168.1.1" - - [17/Jul/2017:17:06:44 +0800] "GET /index.html HTTP/1.0" "192.168.1.136" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko" "192.168.1.1"
即驗證了proxy_set_header Host $host;  $host就是nginx代理服務器,也就是windows客戶端請求的host
2)proxy_set_header Host $proxy_host;

將設置修改成上述proxy_host而後重啓ngxin代理服務器136
[root@wadeson nginx]# sbin/nginx -s reload
從新請求代理頁面:http://192.168.1.136:8080/proxy_path/index.html,而後日誌以下:

首先查看136代理服務器的日誌:
192.168.1.1 - - [18/Jul/2017:10:30:12 +0800] "GET /proxy_path/index.html HTTP/1.1" 192.168.1.136:8080 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"

由於windows是136的客戶端,請求的host爲192.168.223.136:8080,而nginx代理服務器做爲137後端服務器的客戶端,將請求的報文首部從新封裝,
將proxy_host封裝爲請求的host

那麼137上面日誌請求的host就是其自身,proxy_host就是代理服務器請求的host也就是後端服務器137
192.168.1.136 "192.168.1.1" - - [18/Jul/2017:10:30:12 +0800] "GET /index.html HTTP/1.0" "192.168.1.137" 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "192.168.1.1"


3)proxy_set_header Host $host:$proxy_port;
瞭解了上面的知識,那麼此處對應的host就知道表明的啥了,$host表明轉發服務器,$proxy_port表明136轉發服務器請求後端服務器的端口,也就是80。

因而觀察13六、137的日誌進行驗證:
192.168.1.1 - - [18/Jul/2017:10:38:38 +0800] "GET /proxy_path/index.html HTTP/1.1" 192.168.1.136:8080 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"
192.168.1.136 "192.168.1.1" - - [18/Jul/2017:10:38:38 +0800] "GET /index.html HTTP/1.0" "192.168.1.136:80" 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "192.168.1.1"


4)proxy_set_header X-Real-IP $remote_addr;
將$remote_addr的值放進變量X-Real-IP中,此變量名可變,$remote_addr的值爲客戶端的ip

nginx轉發136服務器日誌格式爲:
log_format main '$remote_addr - $remote_user [$time_local] "$request" $http_host '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

nginx後端137服務器的日誌格式:
log_format main '$remote_addr "$http_x_real_ip" - $remote_user [$time_local] "$request" "$http_host" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

二者區別在於"$http_x_real_ip",添加了這個變量的值
從新請求須要訪問的地址http://192.168.1.136:8080/proxy_path/index.html

136的日誌:
192.168.1.1 - - [18/Jul/2017:10:45:07 +0800] "GET /proxy_path/index.html HTTP/1.1" 192.168.1.136:8080 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"

137的日誌:
192.168.1.136 "192.168.1.1" - - [18/Jul/2017:10:45:07 +0800] "GET /index.html HTTP/1.0" "192.168.1.136:80" 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "192.168.1.1"

紅色標記的就是"$http_x_real_ip"的值,便可以看見用戶真實的ip,也就是客戶端的真實ip


5)proxy_set_header X-Forwarded-For $remote_addr;
理解了上面的含義那麼這個封裝報文的意思也就請求了

首先仍是比對136和137的日誌格式:

136代理服務器的日誌格式:
log_format main '$remote_addr - $remote_user [$time_local] "$request" $http_host '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

137後端服務器的日誌格式:
log_format main '$remote_addr "$http_x_real_ip" - $remote_user [$time_local] "$request" "$http_host" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

從新請求須要訪問的地址http://192.168.1.136:8080/proxy_path/index.html

136的日誌顯示:
192.168.1.1 - - [18/Jul/2017:10:51:25 +0800] "GET /proxy_path/index.html HTTP/1.1" 192.168.1.136:8080 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-",最後一個字段
"$http_x_forwarded_for"對應的爲空值

137的日誌顯示:
192.168.1.136 "192.168.1.1" - - [18/Jul/2017:10:51:25 +0800] "GET /index.html HTTP/1.0" "192.168.1.136:80" 304 0 "-" "Mozilla/5.0 
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "192.168.1.1"

能夠看出137後端服務器成功的顯示了真實客戶端的ip

6)proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

五、6二者的區別:
在只有一個代理服務器的轉發的狀況下,二者的效果貌似差很少,均可以真實的顯示出客戶端原始ip

可是區別在於:
$proxy_add_x_forwarded_for變量包含客戶端請求頭中的"X-Forwarded-For",與$remote_addr兩部分,他們之間用逗號分開。


##################################################################################################################
舉個例子,有一個web應用,在它以前經過了兩個nginx轉發,www.kevin.com 即用戶訪問該web經過兩臺nginx。

在第一臺nginx中,使用
proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
 
如今的$proxy_add_x_forwarded_for變量的"X-Forwarded-For"部分是空的,因此只有$remote_addr,而$remote_addr的值是用戶的ip,因而賦值之後,
X-Forwarded-For變量的值就是用戶的真實的ip地址了。

到了第二臺nginx,使用
proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
 
如今的$proxy_add_x_forwarded_for變量,X-Forwarded-For部分包含的是用戶的真實ip,$remote_addr部分的值是上一臺nginx的ip地址,
因而經過這個賦值之後如今的X-Forwarded-For的值就變成了"用戶的真實ip,第一臺nginx的ip",這樣就清楚了吧。

                                                                                                                              
######   Nginx反向代理Tomcat訪問報錯400問題   #######
線上用nginx反向代理tomcat訪問,配置完成後,直接訪問tomcat徹底正常,可是隻要在nginx添加反向代理tomcat,訪問nginx就會報錯400。

緣由和解決辦法:
1)後端服務器設置有相似防盜鏈或者根據http請求頭中的host字段來進行路由或判斷功能的話,若是nginx代理層不重寫請求頭中的host字段,將會致使請求失敗,報400錯誤。
解決辦法:

proxy_set_header Host $http_host;

2)nginx配置中header頭部信息的host不能被配置重了。tomcat沒有對headers中的host進行惟一校驗。
解決辦法(下面兩個要去掉一個):

proxy_set_header Host $host;
proxy_set_header Host $http_host;    #去掉這一行
相關文章
相關標籤/搜索