/* ==================================================================
* Created [2009-4-27 下午11:32:55] by Jon.King
* ==================================================================
* TSS
* ==================================================================
* mailTo:jinpujun@hotmail.com
* Copyright (c) Jon.King, 2009-2012
* ==================================================================
*/
package com.jinhe.tss.core.web.filter;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import com.jinhe.tss.core.exception.BusinessServletException;
import com.jinhe.tss.core.sso.Environment;
import com.jinhe.tss.core.sso.appserver.AppServer;
import com.jinhe.tss.core.sso.context.Context;
import com.jinhe.tss.core.sso.context.RequestContext;
import com.jinhe.tss.core.web.rmi.DominoLogin;
import com.jinhe.tss.core.web.rmi.HttpClientHelper;
/**
* <p> HttpProxyFilter.java </p>
* <p>
* 在HEAD或参数中获取当前请求设置的目标地址所在应用CODE值,如果此CODE值为当前应用CODE,则直接放行;<br/>
* 否则根据此CODE值获取对应的应用地址,将访问地址重新改写后(如果是登陆用户则在Header中添加令牌),<br/>
* 然后通过HttpClient开源工具包以HTTP方式请求新地址,将返回的结果设置到本次请求的响应。<br/>
* <br/>
* 因为本filter无法拦截到访问地址为html(比如OA的首页)的页面(目前配置了拦截.action/.do/.in),所以每次调用html页面前,<br/>
* 可以先发一个 OA/test.do?.... 这样的虚拟请求,以先完成OA等应用的登陆过程,同时取到OA登陆成功返回的token,亦访问OA页面的时使用。<br/>
* <br/>
* 转发的时候把第一次转发过来header头带的appCode值去掉,理论上不会有二次转发的可能。<br/>
* 去掉可以解决这样的问题:当系统1的CMS要访问系统2、系统3等的CMS抓取文章数据时,可以在系统1的CMS的appServer.xml将其它系统的CMS的Code <br/>
* 按别名来配置(比如CMS2、CMS3),这样就可以将请求转发到真实的地址。而转发的时候去掉appCode,防止目标应用接到转发请求时再次转发请求。 <br/>
* 因为如果 appCode=CMS2 转发到 系统2的CMS(appCode=CMS)上的话,CMS2 != CMS,系统2会再次转发该请求。 <br/>
* @see HttpMethod com.jinhe.tss.core.proxy.HttpClientHelper.getHttpMethod(AppServer appServer)
* <br/>
* 注:HttpProxyFilter需要配置在XMLHttpDecodeFilter之前,因为转发请求时将XMLHttp请求的参数流直接转发。<br/>
* 而如果先把XML参数流解析了,则因为该流是一次读完的(读了就没了),转发的时候将不复存在。<br/>
* </p>
*
*/
public class HttpProxyFilter implements Filter {
private static final Logger log = Logger.getLogger(HttpProxyFilter.class);
public void init(FilterConfig config) throws ServletException {
log.info("HTTP请求代理服务初始化完成!appCode=" + Context.getApplicationContext().getCurrentAppCode());
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
try {
String targetAppCode = Context.getRequestContext().getAppCode(); //被请求的目标应用Code
String currentAppCode = Context.getApplicationContext().getCurrentAppCode(); //发送请求当前应用Code
// 没有取到被请求的应用Code,或者被请求的应用为当前应用,则直接请求本应用
if (targetAppCode == null || targetAppCode.equals(currentAppCode)) {
chain.doFilter(request, response);
}
else {
AppServer targetAppServer = Context.getApplicationContext().getAppServer(targetAppCode);
log.info("向目标应用:" + targetAppServer.getName() + "(" + targetAppServer.getCode() + ")" + "转发请求。appCode=" + currentAppCode);
proxy4ForeignApplication(targetAppServer, (HttpServletResponse) response);
}
} catch (Exception e) {
throw new BusinessServletException(e);
}
}
/**
* <p>
* 请求转向不同同服务器的其他应用
* </p>
* @param appServer
* @param req
* @param response
* @throws IOException
* @throws BusinessServletException
*/
private void proxy4ForeignApplication(AppServer appServer, HttpServletResponse response) throws IOException, BusinessServletException {
// 判断如果是转向Domino服务器,验证身份,如果未登录则登录Domino系统
if (appServer.isForeignServer()) {
DominoLogin.instance().loginDominoServer(appServer);
}
HttpClientHelper hcHelper = HttpClientHelper.instance();
HttpClient client = hcHelper.getHttpClient(appServer); // 创建HttpClient对象
HttpState httpState = client.getState();
/* 设置用户令牌相关Cookie,包括一个token Cookie和一个 sessionId Cookie */
boolean isSecure = Context.getRequestContext().isSecure(); //是否https请求
String currentAppCode = Context.getApplicationContext().getCurrentAppCode(); //当前应用code
String domain = appServer.getDomain();
String path = appServer.getPath();
if (Context.isOnline()) {
httpState.addCookie(new Cookie(domain, RequestContext.USER_TOKEN, Context.getToken(), path, null, isSecure)); //token = ****************
}
if (Environment.getSessionId() != null) {
httpState.addCookie(new Cookie(domain, currentAppCode, Environment.getSessionId(), path, null, isSecure)); // TSS = JSessionId
}
HttpMethod httpMethod = hcHelper.getHttpMethod(appServer); // 创建HttpPost对象(等价于一个Post Http Request)
try {
// 请求
int statusCode = client.executeMethod(httpMethod);
if (statusCode == HttpStatus.SC_OK) {
// 转发返回信息
transmitResponse(appServer, response, client, httpMethod);
}
else if ((statusCode == HttpStatus.SC_MOVED_TEMPORARILY)
|| (statusCode == HttpStatus.SC_MOVED_PERMANENTLY)
|| (statusCode == HttpStatus.SC_SEE_OTHER)
|| (statusCode == HttpStatus.SC_TEMPORARY_REDIRECT)) {
dealWithRedirect(appServer, response, client, httpMethod);
}
else {
throw new BusinessServletException(appServer.getName() + "(" + appServer.getCode() + ")连接错误,错误代码:" + statusCode);
}
} finally {
httpMethod.releaseConnection();
}
}
/**
* <p>
* 处理请求自动转向问题
* TODO 转向时未处理request-type/realIp等参数,一般来说不需要处理(需要时再处理)
* </p>
* @param appServer
* @param response
* @param client
* @param httppost
* @throws IOException
* @throws BusinessServletException
*/
private void dealWithRedirect(AppServer appServer, HttpServletResponse response, HttpClient client, HttpMethod httpMethod)
throws IOException, BusinessServletException {
Header location = httpMethod.getResponseHeader("location");
httpMethod.releaseConnection();
String redirectURI = (location == null ? null : location.getValue());
if (redirectURI == null || "".equals(redirectURI)) {
throw new BusinessServletException(appServer.getName() + "(" + appServer.getCode() + ")返回错误的自动转向地址信息");
}
GetMethod redirect = new GetMethod(redirectURI);
try {
client.executeMethod(redirect); // 发送Get请求
transmitResponse(appServer, response, client, redirect);
} finally {
redirect.releaseConnection();
}
}
/**
* <p>
* 转发返回数据,包括转发请求的cookie、Header、以及返回数据流(response)设置到转发前请求的响应对象(response)中。
* </p>
* @param appServer 当前应用相关信息
* @param response 转发前的响应对象(即进本filter时的response)
* @param client 请求客户端对象
* @param httpMethod Http请求
* @throws IOException
*/
private void transmitResponse(AppServer appServer, HttpServletResponse response, HttpClient client,
HttpMethod httpMethod) throws IOException {
// 转发返回的Cookies信息
org.apache.commons.httpclient.Cookie[] cookies = client.getState().getCookies();
for (int i = 0; i < cookies.length; i++) {
String cookieName = cookies[i].getName();
if (cookieName.equals(Context.getApplicationContext().getCurrentAppCode())) continue;
if (cookieName.equals(appServer.getSessionIdName())) {
cookieName = appServer.getCode();
}
javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(cookieName, cookies[i].getValue());
cookie.setPath(Context.getApplicationContext().getCurrentAppServer().getPath());
response.addCookie(cookie);
}
// 转发返回的Header信息
Header contentType = httpMethod.getRequestHeader(HttpClientHelper.CONTENT_TYPE);
if(contentType != null) {
response.setContentType(contentType.getValue());
}
// 转发返回数据流信息
ServletOutputStream out = response.getOutputStream();
InputStream in = httpMethod.getResponseBodyAsStream();
byte[] data = new byte[1024];
int len = 0;
while ((len = in.read(data)) > 0) {
out.write(data, 0, len);
}
out.flush();
out.close();
out = null;
in.close();
in = null;
}
public void destroy() { }
}