/* ==================================================================
* 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.rmi;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.log4j.Logger;
import org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor;
import org.springframework.remoting.httpinvoker.HttpInvokerClientConfiguration;
import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;
import org.springframework.remoting.support.RemoteInvocationResult;
import com.jinhe.tss.core.exception.BusinessException;
import com.jinhe.tss.core.sso.Environment;
import com.jinhe.tss.core.sso.appserver.AppServer;
import com.jinhe.tss.core.sso.appserver.AppServerStorerFactory;
import com.jinhe.tss.core.sso.context.Context;
import com.jinhe.tss.core.sso.context.RequestContext;
/**
* <p>
* 客户端配置远程接口用到本对象,配置时将appCode(如:CMS)以及serviceUrl(如:/remote/PermissionService)设置进来。<br>
* 如此则可以根据appCode值直接取到该应用的BaseURL(如:http://10.100.1.5/cms)值,
* 然后BaseURL + serviceUrl ==> http://10.100.1.5/cms/remote/PermissionService 就是远程接口真实地址。<br>
*
* 本对象主要目的是避免每次应用IP改变时候都要重新改远程接口的配置。<br>
*
* eg:
* HttpInvokerProxyFactory factory = new HttpInvokerProxyFactory();
factory.setServiceUrl("/remote/PermissionService");
factory.setServiceInterface(IPermissionService.class);
factory.setAppCode("CMS");
return (IPermissionService)factory.getObject();
* 即调用远程接口时,在前台发来的请求(request1)的里开启一个新的请求(request2),在JAVA端实现远程调用其他应用里的接口
* </p>
*
*/
public class HttpInvokerProxyFactory extends HttpInvokerProxyFactoryBean {
private String targetAppCode;
private String serviceUrl;
/**
* <p> 设置应用系统编码 </p>
*/
public void setAppCode(String appCode) {
this.targetAppCode = appCode;
}
public void setServiceUrl(String serviceUrl) {
this.serviceUrl = serviceUrl;
}
public void afterPropertiesSet() {
AppServer targetAppServer = AppServerStorerFactory.newInstance().getAppServerStorer().getAppServer(targetAppCode);
super.setServiceUrl(targetAppServer.getBaseURL() + serviceUrl);
setHttpInvokerRequestExecutor(new AutoLoginHttpInvokerRequestExecutor(targetAppServer));
super.afterPropertiesSet();
}
public Object getObject(){
afterPropertiesSet();
return super.getObject();
}
/**
* <p> AutoLoginHttpInvokerRequestExecutor.java </p>
* <p>
* 远程调用执行对象,将Requst中的Cookie信息添加到远程调用当中,实现单点登录;
* 并将远程调用结束后返回的数据和Cookie等信息返回给客户端。
* </p>
*
*/
class AutoLoginHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
private Logger log = Logger.getLogger(AutoLoginHttpInvokerRequestExecutor.class);
private AppServer targetAppServer;
public AutoLoginHttpInvokerRequestExecutor(AppServer server) {
super();
this.targetAppServer = server;
}
protected RemoteInvocationResult doExecuteRequest(HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
throws IOException, ClassNotFoundException {
log.debug(Context.getApplicationContext().getCurrentAppCode() + "【远程调用】调用:" + config.getServiceUrl());
/* 调用远程接口时,在前台发来的请求(request1)的里开启一个新的请求(request2 即 HttpClient),在JAVA端实现远程调用其他应用里的接口 */
HttpClient client = HttpClientHelper.instance().getHttpClient();
HttpState httpState = client.getState();
setRequestCookies(httpState);
PostMethod httpPost = new PostMethod(config.getServiceUrl());
httpPost.addRequestHeader(RequestContext.ANONYMOUS_REQUEST, "true");
httpPost.addRequestHeader(RequestContext.USER_CLIENT_IP, Environment.getClientIp());
httpPost.addRequestHeader("Content-Type", "application/x-java-serialized-object");
httpPost.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray()));
RemoteInvocationResult result = null;
try {
// 执行HTTP请求,访问远程服务
int statusCode = client.executeMethod(httpPost);
if (statusCode == HttpStatus.SC_OK) {
// 处理返回信息
result = super.readRemoteInvocationResult(httpPost.getResponseBodyAsStream(), config.getCodebaseUrl());
// 设置单点登录返回的cookie信息
HttpClientHelper.transmitReturnCookies(httpState.getCookies(), targetAppServer);
} else {
throw new BusinessException(targetAppServer.getName() + "(" + targetAppServer.getCode() + ")连接错误," +
"错误代码:" + statusCode + "\n链接地址:" + config.getServiceUrl());
}
} catch (RuntimeException e) {
throw new BusinessException(targetAppServer.getName() + "(" + targetAppServer.getCode() + ")连接错误," +
"链接地址:" + config.getServiceUrl(), e);
} finally {
httpPost.releaseConnection();
}
return result;
}
/**
* <p> 设置用户身份相关Cookies:token、sessionId等到HttpClient的HttpState中 </p>
* 调用远程接口时,在前台发来的请求(request1)的里开启一个新的请求(request2 = HttpClient.executeMethod(HttpPost)),
* 此处将request1的cookie信息设置给request2,一般情况下,转发的Cookie包括:
* token = ****;JSessionId = 目标应用的sessionId;currentCode = 当前应用的sessionId;等
*
* @param client
*/
private void setRequestCookies(HttpState initialState) {
String cookieDomain = targetAppServer.getDomain();
String cookiePath = targetAppServer.getPath();
boolean secure = false;
HttpServletRequest request = null;
RequestContext requestContext = Context.getRequestContext();
if (requestContext != null) {
request = requestContext.getRequest();
}
if (request != null) {
secure = request.isSecure(); //是否加密请求(https)
/* 设置当前应用SessionId Cookies信息,如果session不存在(比如:注销应用后调用UMS的在线用户库),则不再新创建session */
HttpSession session = request.getSession(false);
if (session != null) {
/* 将当前应用的sessionId命名为currentAppCode的Cookie设置到目标应用的request中(eg:TSS = 123456789 path=/cms)
* 目的是为在目标应用里回访当前应用时使用,那时在当前应用无需新建一个session*/
String currentAppCode = Context.getApplicationContext().getCurrentAppCode();
initialState.addCookie(new Cookie(cookieDomain, currentAppCode, session.getId(), cookiePath, null, secure));
}
javax.servlet.http.Cookie[] cookies = request.getCookies();
if(cookies != null) {
for (int i = 0; i < cookies.length; i++) {
String cookieName = cookies[i].getName();
String sessionIdName = Context.getApplicationContext().getCurrentAppServer().getSessionIdName();
/* “转出应用”的 JSESSIONID 和 token 两cookie不转发到“转入应用”中。待下面处理后才转发 */
if (cookieName.equals(sessionIdName) || cookieName.equals(RequestContext.USER_TOKEN)) continue;
/*
* 将目标应用的sessionId设置到本次请求中来。
* 如:TSS先转发请求到CMS,则CMS的cookie里保留了TSS的JSESSIONID cookie,只是名字由JSESSIONID改为TSS,CMS里再转发请求回TSS的时候,
* 再把该cookie重新改名为JSESSIONID(TSS = 123456789 ==》 JSESSIONID = 123456789),这样在TSS端就无需新建一个session了
*/
if (cookieName.equals(targetAppServer.getCode())) {
cookieName = targetAppServer.getSessionIdName();
}
initialState.addCookie(new Cookie(cookieDomain, cookieName, cookies[i].getValue(), cookiePath, null, secure));
}
}
}
// 设置User Token Cookies信息; TODO 为何不直接转发?是担心request.cookie里不一定有,所以这里统一转发?
String token = Context.getToken();
if (token != null) {
initialState.addCookie(new Cookie(cookieDomain, RequestContext.USER_TOKEN, token, cookiePath, null, secure));
}
}
}
}