Nginx最爲最受歡迎的反向代理和負載均衡服務器,被普遍的應用於互聯網項目中。這不只僅是由於Nginx自己比較輕量,更多的是得益於Nginx的高性能特性,以及支持插件化開發,爲此,不少開發者或者公司基於Nginx開發出了衆多的高性能插件。使用者能夠根據自身的需求來爲Nginx指定某款插件以加強Nginx在某種特定場景下的功能或者提高Nginx在某種特定場景下的性能。html
注意:本文中的客戶端信息指的是:客戶端真實IP、域名、協議、端口。java
Nginx反向代理後,Servlet應用經過request.getRemoteAddr()
取到的IP是Nginx的IP地址,並不是客戶端真實IP,經過request.getRequestURL()
獲取的域名、協議、端口都是Nginx訪問Web應用時的域名、協議、端口,而非客戶端瀏覽器地址欄上的真實域名、協議、端口。nginx
例如在某一臺IP爲192.168.1.100的服務器上,Jetty或者Tomcat端口號爲8080,Nginx端口號80,Nginx反向代理8080端口:apache
server {
listen 80;
location / {
proxy_pass http://127.0.0.1:8080; # 反向代理應用服務器HTTP地址
}
}
複製代碼
在另外一臺機器上用瀏覽器打開http://192.168.1.100/test訪問某個Servlet應用,獲取客戶端IP和URL:後端
System.out.println("RemoteAddr: " + request.getRemoteAddr());
System.out.println("URL: " + request.getRequestURL().toString());
複製代碼
打印的結果信息以下:瀏覽器
RemoteAddr: 127.0.0.1
URL: http://127.0.0.1:8080/test
複製代碼
能夠發現,Servlet程序獲取到的客戶端IP是Nginx的IP而非瀏覽器所在機器的IP,獲取到的URL是Nginx proxy_pass配置的URL組成的地址,而非瀏覽器地址欄上的真實地址。若是將Nginx用做https服務器反向代理後端的http服務,那麼request.getRequestURL()
獲取的URL是http前綴的而非https前綴,沒法獲取到瀏覽器地址欄的真實協議。若是此時將request.getRequestURL()
獲取獲得的URL用做拼接Redirect地址,就會出現跳轉到錯誤的地址,這也是Nginx反向代理時常常出現的一個問題。bash
既然直接使用Nginx獲取客戶端信息存在問題,那咱們該如何解決這個問題呢?服務器
咱們總體上須要從兩個方面來解決這些問題:微信
(1)因爲Nginx是代理服務器,全部客戶端請求都從Nginx轉發到Jetty/Tomcat,若是Nginx不把客戶端真實IP、域名、協議、端口告訴Jetty/Tomcat,那麼Jetty/Tomcat應用永遠不會知道這些信息,因此須要Nginx配置一些HTTP Header來將這些信息告訴被代理的Jetty/Tomcat;併發
(2)Jetty/Tomcat這一端,不能再獲取直接和它鏈接的客戶端(也就是Nginx)的信息,而是要從Nginx傳遞過來的HTTP Header中獲取客戶端信息。
首先,咱們須要在Nginx的配置文件nginx.conf中添加以下配置。
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
複製代碼
各參數的含義以下所示。
Host
包含客戶端真實的域名和端口號;X-Forwarded-Proto
表示客戶端真實的協議(http仍是https);X-Real-IP
表示客戶端真實的IP;X-Forwarded-For
這個Header和X-Real-IP
相似,但它在多層代理時會包含真實客戶端及中間每一個代理服務器的IP。此時,再試一下request.getRemoteAddr()
和request.getRequestURL()
的輸出結果:
RemoteAddr: 127.0.0.1
URL: http://192.168.1.100/test
複製代碼
能夠發現URL好像已經沒問題了,可是IP仍是本地的IP而非真實客戶端IP。可是若是是用Nginx做爲https服務器反向代理到http服務器,會發現瀏覽器地址欄是https前綴可是request.getRequestURL()
獲取到的URL仍是http前綴,也就是僅僅配置Nginx還不能完全解決問題。
僅僅配置Nginx不能完全解決問題,那如何才能解決這個問題呢?一種解決方式就是經過Java方法獲取客戶端信息,例以下面的Java方法。
/*** * 獲取客戶端IP地址;這裏經過了Nginx獲取;X-Real-IP */
public static String getClientIP(HttpServletRequest request) {
String fromSource = "X-Real-IP";
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
fromSource = "X-Forwarded-For";
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
fromSource = "Proxy-Client-IP";
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
fromSource = "WL-Proxy-Client-IP";
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
fromSource = "request.getRemoteAddr";
}
return ip;
}
複製代碼
這種方式雖然可以獲取客戶端的IP地址,可是我總感受這種方式不太友好,由於既然Servlet API提供了request.getRemoteAddr()
方法獲取客戶端IP,那麼不管有沒有用反向代理對於代碼編寫者來講應該是透明的。
接下來,我就分別針對Jetty服務器和Tomcat服務器爲你們介紹下如何進行配置才能更加友好的獲取客戶端信息。
在Jetty服務器的jetty.xml文件中,找到httpConfig
,加入配置:
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
...
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
</Call>
</New>
複製代碼
從新啓動Jetty,再用瀏覽器打開http://192.168.1.100/test測試,結果:
RemoteAddr: 192.168.1.100
URL: http://192.168.1.100/test
複製代碼
此時可發現經過request.getRemoteAddr()
獲取到的IP再也不是127.0.0.1
而是客戶端真實IP,request.getRequestURL()
獲取的URL也是瀏覽器上的真實URL,若是Nginx做爲https代理,request.getRequestURL()
的前綴也會是https。
另外,Jetty將這個功能封裝成一個模塊:http-forwarded。若是不想改jetty.xml配置文件的話,也能夠啓用http-forwarded模塊來實現。
例如能夠經過命令行啓動Jetty:
java -jar start.jar --module=http-forwarded
複製代碼
更多Jetty如何啓用模塊的相關資料能夠參考:www.eclipse.org/jetty/docum…
和Jetty相似,若是使用Tomcat做爲應用服務器,能夠經過配置Tomcat的server.xml文件,在Host元素內最後加入:
<Valve className="org.apache.catalina.valves.RemoteIpValve" />
複製代碼
好了,我們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一塊兒學習一塊兒進步!!
若是你以爲冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公衆號,跟冰河學習高併發、分佈式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公衆號更新了大量技術專題,每一篇技術文章乾貨滿滿!很多讀者已經經過閱讀「 冰河技術 」微信公衆號文章,成功跳槽到大廠;也有很多讀者實現了技術上的飛躍,成爲公司的技術骨幹!若是你也想像他們同樣提高本身的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公衆號吧,天天更新超硬核技術乾貨,讓你對如何提高技術能力再也不迷茫!