本帖旨在讨论hessian的session以及cookie问题,对于hessian的使用网上和官方已经有很多例子,故不在此做讨论。
最近在做一个项目,client面临多种平台的手机客户端以及pc浏览器的flash,在寻找rpc方案的过程中,最初选择使用phprpc,而phprpc没有Objective-c的client版本(Hprose虽有此版,但似乎收费),后转道hessian,hessian使用过程中发现,hessian的client不支持cookie机制,进而导致了其server端不支持共享session,也就是说,由于hessian使用的是http协议且client的实现不支持cookie,从而导致了client对server的每次调用都会建立一个新的session,而如果在每次调用都加入固定的类似jsessionid或userid这种参数的话,似乎看着很不爽,看到网上有篇帖子http://qieqie.iteye.com/blog/82492,看到有人使用aop在http协议层对于使用者透明的增加和剥离这个参数,本人还是觉得不爽,因为我的服务端架构中,已经确定了一个集群方案,而其中对于集群的session准备使用memcached-session-manager,从而要求我在改架构中需要使用标准的http session进行存储。
无奈之下,决定扩展hessian(我使用的是4.0.7),期间曾看到网上高手修改过较早版本的hessian(见贴http://hi.baidu.com/li_zhongnan/blog/item/37fe8ffaefe9ad17a9d31153.html),而这种方式我尝试后发现仍无法使用cookie,后发现原来新版的HessianProxy中的parseResponseHeaders方法已经被废弃使用,于是翻看源码,最后发现在HessianURLConnection(这个类可能在较早的hessian中没有)中有有了一个新的parseResponseHeaders方法,于是扩展开始,为了不修改源码,我自己新建了一个工程,并作为基础工程被我的client所引用,这里我只贴出核心部分的class代码,有兴趣的朋友可以在此基础上进一步完善:
代码中的cookies 之所以使用了static,主要是考虑一个client公用一个session,而非早起的IE浏览器每个进程新开一个session的机制。
客户端调用例子:
修改后经测试,在服务器端可以使用如下代码获取request以及session了:
但问题又来了,我在测试的时候发现,这种获取request及session的方法只有在使用hessian自带的HessianServlet进行服务发布才可用,而如果使用spring mvc和spring的HessianServiceExporter进行hessian的服务发布的话,那么这种获取request和session的方式仍不可用,查看spring3的源码惊奇的发现,在HessianServiceExporter以及HessianExporter的实现中,并没有像hessian的servlet那样调用ServiceContext.begin和ServiceContext.end()方法,难道是spring仔细查看hessian的源码或api,还是说spring不想让开发者拿到servlet容器相关的request和session之类的实例,但不管怎么样,我有这样的需求,于是又扩展了spring的HessianServiceExporter(3之前的版本实现可能稍有区别):
而spring mvc的servlet配置文件中如下:
至此,即便我们使用spring进行发布hessian服务,通过可以通过ServiceContext获取上下文的request等信息了。
以上不论对于hessian client还是对于spring的hessian封装,都是只做增量扩展。
最近在做一个项目,client面临多种平台的手机客户端以及pc浏览器的flash,在寻找rpc方案的过程中,最初选择使用phprpc,而phprpc没有Objective-c的client版本(Hprose虽有此版,但似乎收费),后转道hessian,hessian使用过程中发现,hessian的client不支持cookie机制,进而导致了其server端不支持共享session,也就是说,由于hessian使用的是http协议且client的实现不支持cookie,从而导致了client对server的每次调用都会建立一个新的session,而如果在每次调用都加入固定的类似jsessionid或userid这种参数的话,似乎看着很不爽,看到网上有篇帖子http://qieqie.iteye.com/blog/82492,看到有人使用aop在http协议层对于使用者透明的增加和剥离这个参数,本人还是觉得不爽,因为我的服务端架构中,已经确定了一个集群方案,而其中对于集群的session准备使用memcached-session-manager,从而要求我在改架构中需要使用标准的http session进行存储。
无奈之下,决定扩展hessian(我使用的是4.0.7),期间曾看到网上高手修改过较早版本的hessian(见贴http://hi.baidu.com/li_zhongnan/blog/item/37fe8ffaefe9ad17a9d31153.html),而这种方式我尝试后发现仍无法使用cookie,后发现原来新版的HessianProxy中的parseResponseHeaders方法已经被废弃使用,于是翻看源码,最后发现在HessianURLConnection(这个类可能在较早的hessian中没有)中有有了一个新的parseResponseHeaders方法,于是扩展开始,为了不修改源码,我自己新建了一个工程,并作为基础工程被我的client所引用,这里我只贴出核心部分的class代码,有兴趣的朋友可以在此基础上进一步完善:
- package com.caucho.hessian.client;
- import java.io.IOException;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.net.URLConnection;
- import java.util.List;
- /**
- *
- *
- * @author liliang
- * @descirption 为维持session而扩展的支持cookie的连接
- */
- public class CookieHessianURLConnection extends HessianURLConnection {
- /**
- * @description 共享cookie
- */
- private static List<String> cookies = null;
- CookieHessianURLConnection(URL url, URLConnection conn) {
- super(url, conn);
- }
- @Override
- protected void parseResponseHeaders(HttpURLConnection conn)
- throws IOException {
- super.parseResponseHeaders(conn);
- List<String> _cookies = conn.getHeaderFields().get("Set-Cookie");
- if (_cookies != null)
- cookies = _cookies;
- }
- public void addRequestHeaders() {
- if (cookies != null) {
- for (String cookieString : cookies) {
- addHeader("Cookie", cookieString);
- }
- }
- }
- }
package com.caucho.hessian.client;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
/**
*
*
* @author liliang
* @descirption 为维持session而扩展的支持cookie的连接
*/
public class CookieHessianURLConnection extends HessianURLConnection {
/**
* @description 共享cookie
*/
private static List<String> cookies = null;
CookieHessianURLConnection(URL url, URLConnection conn) {
super(url, conn);
}
@Override
protected void parseResponseHeaders(HttpURLConnection conn)
throws IOException {
super.parseResponseHeaders(conn);
List<String> _cookies = conn.getHeaderFields().get("Set-Cookie");
if (_cookies != null)
cookies = _cookies;
}
public void addRequestHeaders() {
if (cookies != null) {
for (String cookieString : cookies) {
addHeader("Cookie", cookieString);
}
}
}
}
- package com.caucho.hessian.client;
- import java.io.IOException;
- import java.net.URL;
- import java.net.URLConnection;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- /**
- *
- *
- * @author liliang
- * @descirption 连接工厂类 主要使用扩展后的CookieHessianURLConnection来维持连接
- * open方法除返回值外完全摘自源码HessianURLConnectionFactory
- */
- public class CookieHessianURLConnectionFactory extends
- AbstractHessianConnectionFactory {
- private static final Logger log = Logger
- .getLogger(CookieHessianURLConnectionFactory.class.getName());
- @Override
- public HessianConnection open(URL url) throws IOException {
- if (log.isLoggable(Level.FINER))
- log.finer(this + " open(" + url + ")");
- URLConnection conn = url.openConnection();
- // HttpURLConnection httpConn = (HttpURLConnection) conn;
- // httpConn.setRequestMethod("POST");
- // conn.setDoInput(true);
- long connectTimeout = getHessianProxyFactory().getConnectTimeout();
- if (connectTimeout >= 0)
- conn.setConnectTimeout((int) connectTimeout);
- conn.setDoOutput(true);
- long readTimeout = getHessianProxyFactory().getReadTimeout();
- if (readTimeout > 0) {
- try {
- conn.setReadTimeout((int) readTimeout);
- } catch (Throwable e) {
- }
- }
- /*
- * // Used chunked mode when available, i.e. JDK 1.5. if
- * (_proxyFactory.isChunkedPost() && conn instanceof HttpURLConnection)
- * { try { HttpURLConnection httpConn = (HttpURLConnection) conn;
- *
- * httpConn.setChunkedStreamingMode(8 * 1024); } catch (Throwable e) { }
- * }
- */
- return new CookieHessianURLConnection(url, conn);
- }
- }
package com.caucho.hessian.client;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*
* @author liliang
* @descirption 连接工厂类 主要使用扩展后的CookieHessianURLConnection来维持连接
* open方法除返回值外完全摘自源码HessianURLConnectionFactory
*/
public class CookieHessianURLConnectionFactory extends
AbstractHessianConnectionFactory {
private static final Logger log = Logger
.getLogger(CookieHessianURLConnectionFactory.class.getName());
@Override
public HessianConnection open(URL url) throws IOException {
if (log.isLoggable(Level.FINER))
log.finer(this + " open(" + url + ")");
URLConnection conn = url.openConnection();
// HttpURLConnection httpConn = (HttpURLConnection) conn;
// httpConn.setRequestMethod("POST");
// conn.setDoInput(true);
long connectTimeout = getHessianProxyFactory().getConnectTimeout();
if (connectTimeout >= 0)
conn.setConnectTimeout((int) connectTimeout);
conn.setDoOutput(true);
long readTimeout = getHessianProxyFactory().getReadTimeout();
if (readTimeout > 0) {
try {
conn.setReadTimeout((int) readTimeout);
} catch (Throwable e) {
}
}
/*
* // Used chunked mode when available, i.e. JDK 1.5. if
* (_proxyFactory.isChunkedPost() && conn instanceof HttpURLConnection)
* { try { HttpURLConnection httpConn = (HttpURLConnection) conn;
*
* httpConn.setChunkedStreamingMode(8 * 1024); } catch (Throwable e) { }
* }
*/
return new CookieHessianURLConnection(url, conn);
}
}
- public class MyHessianProxy extends HessianProxy {
- public MyHessianProxy(URL url, HessianProxyFactory factory, Class<?> api) {
- super(url, factory, api);
- }
- /** Add cookie list to request headers */
- @Override
- protected void addRequestHeaders(HessianConnection conn) {
- super.addRequestHeaders(conn);
- if(conn instanceof CookieHessianURLConnection) {
- CookieHessianURLConnection connection = (CookieHessianURLConnection)conn;
- connection.addRequestHeaders();
- }
- }
- }
public class MyHessianProxy extends HessianProxy {
public MyHessianProxy(URL url, HessianProxyFactory factory, Class<?> api) {
super(url, factory, api);
}
/** Add cookie list to request headers */
@Override
protected void addRequestHeaders(HessianConnection conn) {
super.addRequestHeaders(conn);
if(conn instanceof CookieHessianURLConnection) {
CookieHessianURLConnection connection = (CookieHessianURLConnection)conn;
connection.addRequestHeaders();
}
}
}
- package com.mostar.common.hessian;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- import java.net.URL;
- import com.caucho.hessian.client.HessianProxyFactory;
- import com.caucho.hessian.io.HessianRemoteObject;
- public class MyHessianProxyFactory extends HessianProxyFactory {
- @Override
- public Object create(Class<?> api, URL url, ClassLoader loader) {
- if (api == null)
- throw new NullPointerException(
- "api must not be null for HessianProxyFactory.create()");
- InvocationHandler handler = null;
- handler = new MyHessianProxy(url, this, api);
- return Proxy.newProxyInstance(loader, new Class[] { api,
- HessianRemoteObject.class }, handler);
- }
- }
package com.mostar.common.hessian;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.URL;
import com.caucho.hessian.client.HessianProxyFactory;
import com.caucho.hessian.io.HessianRemoteObject;
public class MyHessianProxyFactory extends HessianProxyFactory {
@Override
public Object create(Class<?> api, URL url, ClassLoader loader) {
if (api == null)
throw new NullPointerException(
"api must not be null for HessianProxyFactory.create()");
InvocationHandler handler = null;
handler = new MyHessianProxy(url, this, api);
return Proxy.newProxyInstance(loader, new Class[] { api,
HessianRemoteObject.class }, handler);
}
}
代码中的cookies 之所以使用了static,主要是考虑一个client公用一个session,而非早起的IE浏览器每个进程新开一个session的机制。
客户端调用例子:
- // 设置连接工厂类
- System.setProperty(HessianConnectionFactory.class.getName(),
- CookieHessianURLConnectionFactory.class.getName());
- String url = "http://10.0.8.1:8080/test/hello.service";
- MyHessianProxyFactory factory = new MyHessianProxyFactory();
- IHellowordService hellowordService = (IHellowordService) factory
- .create(IHellowordService.class, url);
- System.out.println(hellowordService.sayHello("myname"));
// 设置连接工厂类
System.setProperty(HessianConnectionFactory.class.getName(),
CookieHessianURLConnectionFactory.class.getName());
String url = "http://10.0.8.1:8080/test/hello.service";
MyHessianProxyFactory factory = new MyHessianProxyFactory();
IHellowordService hellowordService = (IHellowordService) factory
.create(IHellowordService.class, url);
System.out.println(hellowordService.sayHello("myname"));
修改后经测试,在服务器端可以使用如下代码获取request以及session了:
- HttpServletRequest req = (HttpServletRequest) ServiceContext
- .getContextRequest();
- req.getSession();
HttpServletRequest req = (HttpServletRequest) ServiceContext
.getContextRequest();
req.getSession();
但问题又来了,我在测试的时候发现,这种获取request及session的方法只有在使用hessian自带的HessianServlet进行服务发布才可用,而如果使用spring mvc和spring的HessianServiceExporter进行hessian的服务发布的话,那么这种获取request和session的方式仍不可用,查看spring3的源码惊奇的发现,在HessianServiceExporter以及HessianExporter的实现中,并没有像hessian的servlet那样调用ServiceContext.begin和ServiceContext.end()方法,难道是spring仔细查看hessian的源码或api,还是说spring不想让开发者拿到servlet容器相关的request和session之类的实例,但不管怎么样,我有这样的需求,于是又扩展了spring的HessianServiceExporter(3之前的版本实现可能稍有区别):
- /**
- *
- *
- * @author liliang
- * @descirption 对spring的HessianServiceExporter进行扩展,以能够在逻辑service中使用httpRequest
- */
- public class HessianServiceContextExporter extends HessianServiceExporter {
- @Override
- public void handleRequest(HttpServletRequest request,
- HttpServletResponse response) throws ServletException, IOException {
- try {
- //
- String serviceId = request.getPathInfo();
- String objectId = request.getParameter("id");
- if (objectId == null)
- objectId = request.getParameter("ejbid");
- ServiceContext.begin(request, serviceId, objectId);
- // 调用spring自身的实现
- super.handleRequest(request, response);
- } finally {
- ServiceContext.end();
- }
- }
- }
/**
*
*
* @author liliang
* @descirption 对spring的HessianServiceExporter进行扩展,以能够在逻辑service中使用httpRequest
*/
public class HessianServiceContextExporter extends HessianServiceExporter {
@Override
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
//
String serviceId = request.getPathInfo();
String objectId = request.getParameter("id");
if (objectId == null)
objectId = request.getParameter("ejbid");
ServiceContext.begin(request, serviceId, objectId);
// 调用spring自身的实现
super.handleRequest(request, response);
} finally {
ServiceContext.end();
}
}
}
而spring mvc的servlet配置文件中如下:
- <bean name="/hello.service"
- class="org.springframework.remoting.caucho.HessianServiceContextExporter">
- <property name="service" ref="hellowordService" />
- <property name="serviceInterface"
- value="com.mostar.sng.service.sample.IHellowordService" />
- </bean>
<bean name="/hello.service"
class="org.springframework.remoting.caucho.HessianServiceContextExporter">
<property name="service" ref="hellowordService" />
<property name="serviceInterface"
value="com.mostar.sng.service.sample.IHellowordService" />
</bean>
至此,即便我们使用spring进行发布hessian服务,通过可以通过ServiceContext获取上下文的request等信息了。
以上不论对于hessian client还是对于spring的hessian封装,都是只做增量扩展。