为什么80%的码农都做不了架构师?>>>
最近在实施apache2.2+mod_proxy+jetty7.2.0时遇到的一个诡异的问题
现象:
直接访问应用(A)能正常显示页面;但是通过应用(B)再代理访问A,则页面显示出错。
A的域名benni82.a.com
===============
B的域名www.b.comapache rewrite配置如下:
RewriteRule ^/proxy/(.*)$ $1 [P,L]
用户通过访问 http://www.b.com/proxy/http://benni82.a.com 来访问应用A,就显示错误页面。
而用户直接访问 http://benni.a.com 正常显示页面。
A应用的逻辑上依赖request.getServerName()
分析:
当通过mod_proxy代理访问时,http请求头会带上一些额外的信息
X-Forwarded-For: 10.20.156.2, 10.20.156.3
X-Forwarded-Host: www.b.com, benni82.a.com
X-Forwarded-Server: www.b.com, www.a.com
X-Forwarded-For的内容从左到右依次是,用户的ip,第一个代理ip
X-Forwarded-Host依次是,第一次代理时请求头的Host值,第二次代理时请求头的Host值
X-Forwarded-Server依次是:第一个代理的ServerName,第二个代理的ServerName(取自apache ServerName指令的设值)
而为了解决获取客户端原始ip地址,我们开启了jetty的forwarded功能。
开启该功能后,jetty在解析请求头时会优先使用X-Forwarded的内容。
- 会拿X-Forwarded-For最左边的ip地址(10.20.156.2)作为客户端原始ip,设置request.remoteAddr
- 会拿X-Forwarded-Host的最左边的值(www.b.com),设置request.serverName,
- 如果X-Forwarded-Host没有内容,会拿X-Forwarded-Server的最左边的值(crm.cn.alibaba-inc.com),设置request.serverName。
所以最后应用获取的ServerName时第一次代理时的Host,即www.b.com,页面返回错误。
而直接访问http://benni82.a.com时,
相应的值如下:
X-Forwarded-Host: benni82.a.com
X-Forwarded-Server: www.a.com
最后应用获取的serverName是benni82.a.com,和请求头的host一致,页面能正常返回。
解决方案:
connector配置中添加两行
<Call name="addConnector"><Arg><New class="org.eclipse.jetty.server.nio.SelectChannelConnector"><Set name="port"><Property name="jetty.port" default="7001"/></Set><Set name="forwarded">true</Set><Set name="forwardedServerHeader">ignore</Set><Set name="forwardedHostHeader">ignore</Set><Set name="maxIdleTime">600000</Set></New></Arg>
</Call>
forwardedServerHeader默认是: X-Forwarded-Server
forwardedHostHeader默认值: X-Forwarded-Host
目的阻止jetty用 X-Forwarded-Host或 X-Forwarded-Server的内容复写ServerName .
protected void checkForwardedHeaders(EndPoint endpoint, Request request) throws IOException{HttpFields httpFields = request.getConnection().getRequestFields();// Retrieving headers from the requestString forwardedHost = getLeftMostValue(httpFields.getStringField(getForwardedHostHeader()));String forwardedServer = getLeftMostValue(httpFields.getStringField(getForwardedServerHeader()));String forwardedFor = getLeftMostValue(httpFields.getStringField(getForwardedForHeader()));String forwardedProto = getLeftMostValue(httpFields.getStringField(getForwardedProtoHeader()));if (_hostHeader != null){// Update host headerhttpFields.put(HttpHeaders.HOST_BUFFER,_hostHeader);request.setServerName(null);request.setServerPort(-1);request.getServerName();}else if (forwardedHost != null){// Update host headerhttpFields.put(HttpHeaders.HOST_BUFFER,forwardedHost);request.setServerName(null);request.setServerPort(-1);request.getServerName();}else if (forwardedServer != null){// Use provided server namerequest.setServerName(forwardedServer);}if (forwardedFor != null){request.setRemoteAddr(forwardedFor);InetAddress inetAddress = null;if (_useDNS){try{inetAddress = InetAddress.getByName(forwardedFor);}catch (UnknownHostException e){Log.ignore(e);}}request.setRemoteHost(inetAddress == null?forwardedFor:inetAddress.getHostName());}if (forwardedProto != null){request.setScheme(forwardedProto);}}