在tomcat应用中获得原始IP

Apache/Nginx 通常被放在tomcat前作为http代理。例如:

browser -> apache -> tomcat

但是缺点是丢失了很多原有的网络信息:ip、hostname、protocol(用的是http还是 https)

比如,当java程序想生成http重定向请求的时候会遇到麻烦。因为HTTP头部的Location字段的值必须是绝对URL。重定向的问题勉强可以交给前面的proxy来解决(让apache重写header),但是藏在http body中的各种链接问题就棘手多了。比如当从一个html页面载入一个外部资源时,资源的链接到底是写http的还是https的呢?

通过修改apache和tomcat的配置,可以让这个问题得到较为完美的解决。

首先是要apache把这些丢失的信息通过http header发到后面去。

一. Host字段

HTTP头部的Host字段用来写网站的域名。如果服务器(此处指tomcat)需要支持virtual host,即同一个IP服务多个域名,那么这个字段很重要。

在配置mod_proxy时加上ProxyPreserveHost,就会让apache往后端转发的时候,Host字段依然填它从browser收到的那个域名。

二. Apache默认会发送的字段

默认情况下apache在做http逆向代理时会给后端发送以下字段

  • X-Forwarded-For client的IP地址
  • X-Forwarded-Host client所请求的域名,即client发来的的http header中Host字段的值。
  • X-Forwarded-Server 这台代理服务器(apache)的域名。

三. 需要添加的

纵使有了以上信息,我们还是不知道client用的到底是http还是https访问的apache。所以要加下面这行:

RequestHeader set X-Forwarded-Proto "https" env=HTTPS

这行代码的意思是,如果当前含有"HTTPS"这个环境变量,那么往后端转发请求时,在头部加上X-Forwarded-Proto字段,值为"https"。

类似的还可添加其它信息。如HTTPS/SPDY的具体信息,假如前端用的是spdy,那么当前请求不仅具有HTTPS这个环境变量,还有一个名为SPDY_VERSION的环境变量,它的值就是spdy 的版本号。

RequestHeader set X-SPDY-VERSION "%{SPDY_VERSION}e" env=SPDY_VERSION

等等。其它需要什么添加什么。如果想知道apache有哪些环境变量,把以下三行保存为php文件放到apache上,然后用浏览器访问一下就知道了。

<?php
phpinfo();
?>;

其次,是让tomcat理解这些额外的header。

tomcat的Valves,能在收到请求时拦截并修改相应的值。https://tomcat.apache.org/tomcat-7.0-doc/config/valve.html

其中与我在此讨论的问题相关的是org.apache.catalina.valves.RemoteIpValve。它的主要功能是:

  1. replaces the apparent client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request headers (e.g. "X-Forwarded-For").

  2. replaces the apparent scheme (http/https) and server port with the scheme presented by a proxy or a load balancer via a request header (e.g. "X-Forwarded-Proto").

简单点说,就是把下面这行代码添加到tomcat的server.xml中的Engine或Host element下。

<Valve className="org.apache.catalina.valves.RemoteIpValve"    remoteIpHeader="x-forwarded-for"    proxiesHeader="x-forwarded-by"    protocolHeader="x-forwarded-proto" />

然后写个servlet测试下,看看对不对:

public class DumpHeaders extends HttpServlet {
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
    response.setContentType("text/plain");
    response.setCharacterEncoding("UTF-8");
    try(java.io.PrintWriter writer=response.getWriter()){
      writer.println("isSecure:"+request.isSecure());
      writer.println("server name:"+request.getServerName());
      writer.println("remote addr:"+request.getRemoteAddr());
      writer.println("headers:");
      java.util.Enumeration<String> names=request.getHeaderNames();
      while(names.hasMoreElements()){
        String headerName=names.nextElement();
        java.util.Enumeration<String> values=request.getHeaders(headerName);
        while(values.hasMoreElements()){
          String headerValue=values.nextElement();
          writer.println(headerName+"\t"+headerValue);
        }
      }
    }
  }
}

HTTP下输出为:
isSecure:false
server name:www.sunchangming.com
remote addr:60.10.169.130
headers:
host www.sunchangming.com
accept text/html, application/xhtml+xml, */*
accept-language zh-CN
user-agent Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
accept-encoding gzip, deflate
cache-control no-cache
connection Keep-Alive
x-forwarded-host www.sunchangming.com
x-forwarded-server www.sunchangming.com

HTTPS下输出为:
isSecure:true
server name:www.sunchangming.com
remote addr:60.10.169.130
headers:
host www.sunchangming.com
accept text/html, application/xhtml+xml, */*
accept-language zh-CN
user-agent Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
accept-encoding gzip, deflate
cache-control no-cache
x-forwarded-proto https
connection Keep-Alive
x-forwarded-host www.sunchangming.com
x-forwarded-server www.sunchangming.com

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥