昨天在做配置转换的时候发现的这个问题,
简单描述一下吧:
之前的架构是:前端apache,后端Jboss,apache使用mod_jk转发请求到后端。
现在的架构是:前端nginx,后端Jboss,nginx作为reverse proxy把请求调度到后端。(那几个中文会被屏蔽!)
web server前还有个NAT设备,提供VIP给客户端链接,转换规则是: VIP:9999 = RIP:80
也就是说用户需要用http://nigel.zeng.me:9999的URL才能访问到我web server提供的80端口服务,
在使用apache时,网站可以正常来访问:
可以看到这里有3个302跳转:
第一个:我访问http://pmine.xxx.xxx:9999/,会被后端的一个filter拦截,跳转到https://ark.xxx.xx:4430/arkserver/Login.aspx?app=http://pmine.xxxx.xxxx:9999
第二个:认证通过,再从“https://ark.xxx.xx:4430/arkserver/Login.aspx?app=http://pmine.xxxx.xxxx:9999 ”这串URL里提取出app=http://pmine.xxxx.xxxx:9999 的 “http://pmine.xxxx.xxxx:9999 ”,跳转到这个页面。(这个页面就是刚开始我们访问的页面)
第三个:访问http://pmine.xxxx.xxxx:9999 ,被应用重定向到http://pmine.xxxx.xxxx:9999/console.htm,完成页面展现。
我按照apache的规则将配置修改到nginx,(nginx配置上没有错误,实现的功能完全是一样的)。
结果却完全不一样,页面不能访问了:
第一次跳转的时候,参数里的原始URL变成了7001端口,而不是我访问时的9999端口,所以当认证完成再跳回第一次访问的URL时,就变成了http://pmine.xxx.xxx.xx:7001,而不是http://pmine.xxx.xxx.xx:9999。
所以导致这个访问没有服务器应答,因为我们监听的是9999端口。
流程大致是这样的:
------------------------------------------------------------------------------------- 丑陋的分割线 ------------------------------------------------------
问题描述完了,那么原因是什么呢?
在第一次做跳转,拼凑URL的时候,代码里是这么写的:
端口是 userPort这个变量,它取值的逻辑是,如果配置里没有配置端口,那么久使用getLocalPort来获取。
request.getLocalPort()方法会获得请求头里的端口。
显然,使用apache时,这里获取到的是 9999端口,而是用nginx时,这里获取到的是7001端口。
究其原因(按照我的理解,apache和tomcat内部的机制我没有深入了解):
1、apache与tomcat使用mod_jk来进行的请求转发,是在内部进行的,并没有对数据包的目的IP和目的端口进行修改,所以当后端的Jboss解析这个请求的时候,会解析得到的是原始请求的9999端口。
2、nginx与tomcat的连接是有nginx在前端做转发,通过“proxy_pass:http://127.0.0.1:7001”指令来进行操作,我询问过做nginx开发的一个同事,这里做转发的时候会修改请求的数据包,把目的IP和目的端口修改掉,相当于是nginx本身向后端jboss发起的针对“http://127.0.0.1:7001”这个URL的请求,所以后端的JBoss使用getLocalPort()来获取端口时,得到的就是这个请求URL里的7001端口。
所以,拼凑起来的URL,在apache时是正确的原始URL,在nginx时这是端口为7001的错误URL,这就导致在进行认证后用户无法跳转到当初的访问页面,出现访问无响应的情况。
------------------------------------------------------------------------------------- 丑陋的分割线 ------------------------------------------------------
那么解决办法应该可以有两个:
1、在应用的配置项里指定appPort,本文中的情况则需要修改成9999
2、修改getLocalPort()这个获取方式,可以换一种拼凑URL的方式,比如从请求头里的$HOST变量里获取。(针对nginx,需要加上一些配置项,把原始请求的HOST传递到后端,使用proxy_set_head参数。感兴趣的可以交流一下)。
that's all。