项目选定Hessian作为web
service的实现方式,确实很轻量级,速度就跟直接用socket差不多,全是二进制传送节约了不少开销。但是在使用过程中有业务需要是必须获得远程
端的ip地址,主机名等信息的。翻便Hessian的文档和google了n次未果,迫不得已到caucho和spring论坛去问,都没有得到答复。今
天心一横把hessian的源代码加入到项目中单步跟踪,总算有点小收获。献丑分享出来,一方面给需要的朋友,主要还是希望各位找找是否存在bug,以及
是否有更好的改良。
一:先撇开Spring不谈,来看看纯Hessian的调用
按照hessian文档里边介绍的demo,在web.xml里边如下配置
- <servlet>
- <servlet-name>hello</servlet-name>
- <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
- <init-param>
- <param-name>home-class</param-name>
- <param-value>example.BasicService</param-value>
- </init-param>
- <init-param>
- <param-name>home-api</param-name>
- <param-value>example.Basic</param-value>
- </init-param>
- </servlet>
-
- <servlet-mapping>
- <url-pattern>/hello</url-pattern>
- <servlet-name>hello</servlet-name>
- </servlet-mapping>
由此可知Hessian调用的入口是HessianServlet这个Servlet,进去看看
-
-
-
- public class HessianServlet extends GenericServlet {
- private Class _homeAPI;
- private Object _homeImpl;
-
- private Class _objectAPI;
- private Object _objectImpl;
-
- private HessianSkeleton _homeSkeleton;
- private HessianSkeleton _objectSkeleton;
-
- private SerializerFactory _serializerFactory;
-
- public String getServletInfo()
- {
- return "Hessian Servlet";
- }
-
-
-
-
- public void setHomeAPI(Class api)
- {
- _homeAPI = api;
- }
-
-
-
-
- public void setHome(Object home)
- {
- _homeImpl = home;
- }
-
-
-
-
- public void setObjectAPI(Class api)
- {
- _objectAPI = api;
- }
-
-
-
-
- public void setObject(Object object)
- {
- _objectImpl = object;
- }
-
-
-
-
- public void setService(Object service)
- {
- setHome(service);
- }
-
-
-
-
- public void setAPIClass(Class api)
- {
- setHomeAPI(api);
- }
-
-
-
-
- public Class getAPIClass()
- {
- return _homeAPI;
- }
-
-
-
-
- public void setSerializerFactory(SerializerFactory factory)
- {
- _serializerFactory = factory;
- }
-
-
-
-
- public SerializerFactory getSerializerFactory()
- {
- if (_serializerFactory == null)
- _serializerFactory = new SerializerFactory();
-
- return _serializerFactory;
- }
-
-
-
-
- public void setSendCollectionType(boolean sendType)
- {
- getSerializerFactory().setSendCollectionType(sendType);
- }
-
-
-
-
- public void init(ServletConfig config)
- throws ServletException
- {
- super.init(config);
-
- try {
- if (_homeImpl != null) {
- }
- else if (getInitParameter("home-class") != null) {
- String className = getInitParameter("home-class");
-
- Class homeClass = loadClass(className);
-
- _homeImpl = homeClass.newInstance();
-
- init(_homeImpl);
- }
- else if (getInitParameter("service-class") != null) {
- String className = getInitParameter("service-class");
-
- Class homeClass = loadClass(className);
-
- _homeImpl = homeClass.newInstance();
-
- init(_homeImpl);
- }
- else {
- if (getClass().equals(HessianServlet.class))
- throw new ServletException("server must extend HessianServlet");
-
- _homeImpl = this;
- }
-
- if (_homeAPI != null) {
- }
- else if (getInitParameter("home-api") != null) {
- String className = getInitParameter("home-api");
-
- _homeAPI = loadClass(className);
- }
- else if (getInitParameter("api-class") != null) {
- String className = getInitParameter("api-class");
-
- _homeAPI = loadClass(className);
- }
- else if (_homeImpl != null) {
- _homeAPI = findRemoteAPI(_homeImpl.getClass());
-
- if (_homeAPI == null)
- _homeAPI = _homeImpl.getClass();
- }
-
- if (_objectImpl != null) {
- }
- else if (getInitParameter("object-class") != null) {
- String className = getInitParameter("object-class");
-
- Class objectClass = loadClass(className);
-
- _objectImpl = objectClass.newInstance();
-
- init(_objectImpl);
- }
-
- if (_objectAPI != null) {
- }
- else if (getInitParameter("object-api") != null) {
- String className = getInitParameter("object-api");
-
- _objectAPI = loadClass(className);
- }
- else if (_objectImpl != null)
- _objectAPI = _objectImpl.getClass();
-
- _homeSkeleton = new HessianSkeleton(_homeImpl, _homeAPI);
- if (_objectAPI != null)
- _homeSkeleton.setObjectClass(_objectAPI);
-
- if (_objectImpl != null) {
- _objectSkeleton = new HessianSkeleton(_objectImpl, _objectAPI);
- _objectSkeleton.setHomeClass(_homeAPI);
- }
- else
- _objectSkeleton = _homeSkeleton;
- } catch (ServletException e) {
- throw e;
- } catch (Exception e) {
- throw new ServletException(e);
- }
- }
-
- private Class findRemoteAPI(Class implClass)
- {
- if (implClass == null || implClass.equals(GenericService.class))
- return null;
-
- Class []interfaces = implClass.getInterfaces();
-
- if (interfaces.length == 1)
- return interfaces[0];
-
- return findRemoteAPI(implClass.getSuperclass());
- }
-
- private Class loadClass(String className)
- throws ClassNotFoundException
- {
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
-
- if (loader != null)
- return Class.forName(className, false, loader);
- else
- return Class.forName(className);
- }
-
- private void init(Object service)
- throws ServletException
- {
- if (service instanceof Service)
- ((Service) service).init(getServletConfig());
- else if (service instanceof Servlet)
- ((Servlet) service).init(getServletConfig());
- }
-
-
-
-
-
- public void service(ServletRequest request, ServletResponse response)
- throws IOException, ServletException
- {
- HttpServletRequest req = (HttpServletRequest) request;
- HttpServletResponse res = (HttpServletResponse) response;
-
- if (! req.getMethod().equals("POST")) {
- res.setStatus(500, "Hessian Requires POST");
- PrintWriter out = res.getWriter();
-
- res.setContentType("text/html");
- out.println("<h1>Hessian Requires POST</h1>");
-
- return;
- }
-
- String serviceId = req.getPathInfo();
- String objectId = req.getParameter("id");
- if (objectId == null)
- objectId = req.getParameter("ejbid");
-
- ServiceContext.begin(req, serviceId, objectId);
-
- try {
- InputStream is = request.getInputStream();
- OutputStream os = response.getOutputStream();
-
- Hessian2Input in = new Hessian2Input(is);
- AbstractHessianOutput out;
-
- SerializerFactory serializerFactory = getSerializerFactory();
-
- in.setSerializerFactory(serializerFactory);
-
- int code = in.read();
-
- if (code != 'c') {
-
- throw new IOException("expected 'c' in hessian input at " + code);
- }
-
- int major = in.read();
- int minor = in.read();
-
- if (major >= 2)
- out = new Hessian2Output(os);
- else
- out = new HessianOutput(os);
-
- out.setSerializerFactory(serializerFactory);
-
- if (objectId != null)
- _objectSkeleton.invoke(in, out);
- else
- _homeSkeleton.invoke(in, out);
-
- out.close();
- } catch (RuntimeException e) {
- throw e;
- } catch (ServletException e) {
- throw e;
- } catch (Throwable e) {
- throw new ServletException(e);
- } finally {
- ServiceContext.end();
- }
- }
- }
先看init()函数,功能还是一样,初始话一些东西,读入init-param的内容,并且load这些init-param的class
主要的还是service()函数
在service函数里边会获得request和response对象的输入和输出流,用来构造Hessian2Input和Hessian2Output,Hessian就是解析这两个东西来执行函数调用的。当然,在service里边还有一个重要的语句
- ServiceContext.begin(req, serviceId, objectId);
这个函数有点奇怪,我每次到这里serviceId和objectId都是空,不知道是不是历史遗留问题还存在这两个参数。
进去这个类看看
原来ServiceContext 是用来保存当前调用线程的上下文的,比如request对象等(不知道这个解释对不对)。有了这个东西就太好了,因为里边有request,就有了调用端的一切信息,呵呵。
继续回来看那个Servlet,到了真正调用的时候了,也就是这段代码
- if (objectId != null)
- ctSkeleton.invoke(in, out);
- else
- Skeleton.invoke(in, out);
跟踪invoke方法看看真面目
- public void invoke(AbstractHessianInput in, AbstractHessianOutput out)
- throws Throwable
- {
- ServiceContext context = ServiceContext.getContext();
-
- String header;
- while ((header = in.readHeader()) != null) {
- Object value = in.readObject();
-
- context.addHeader(header, value);
- }
- String ip = context.getContextRequest().getRemoteAddr();
- String methodName = in.readMethod();
- Method method = getMethod(methodName);
-
- if (method != null) {
- }
- else if ("_hessian_getAttribute".equals(methodName)) {
- String attrName = in.readString();
- in.completeCall();
-
- String value = null;
-
- if ("java.api.class".equals(attrName))
- alue = getAPIClassName();
- else if ("java.home.class".equals(attrName))
- alue = getHomeClassName();
- else if ("java.object.class".equals(attrName))
- alue = getObjectClassName();
-
- out.startReply();
-
- out.writeObject(value);
-
- out.completeReply();
- return;
- }
- else if (method == null) {
- out.startReply();
- out.writeFault("NoSuchMethodException",
- "The service has no method named: " + in.getMethod(),
- null);
- out.completeReply();
- return;
- }
-
- Class []args = method.getParameterTypes();
- Object []values = new Object[args.length];
-
-
-
- for (int i = 0; i < args.length; i++){
- if(i == args.length-1){
- values[i] = in.readObject(args[i], ip);
- }else{
- values[i] = in.readObject(args[i]);
- }
-
- }
-
-
- in.completeCall();
-
- Object result = null;
-
- try {
- result = method.invoke(_service, values);
- } catch (Throwable e) {
- if (e instanceof InvocationTargetException)
- e = ((InvocationTargetException) e).getTargetException();
-
- log.log(Level.WARNING, e.toString(), e);
-
- out.startReply();
- out.writeFault("ServiceException", e.getMessage(), e);
- out.completeReply();
- return;
- }
-
- out.startReply();
-
- out.writeObject(result);
-
- out.completeReply();
- }
就是在这个方法里边,hessian把包装过的输入输出流当作参数传入并进行解析的,看看这个函数的第一句,正是取得ServiceContext的地方,此时应该就是把刚才Servlet里边保存的上下文取出来使用。
这个时候出现了第一个hack的地方 - String ip = context.getContextRequest().getRemoteAddr();
在此处我取得远程的ip地址保存起来。然后在第二个hack的地方
- Class []args = method.getParameterTypes();
- Object []values = new Object[args.length];
-
-
-
- for (int i = 0; i < args.length; i++){
- if(i == args.length-1){
- values[i] = in.readObject(args[i], ip);
- }else{
- values[i] = in.readObject(args[i]);
- }
-
- }
我用这个ip地址取代最后一个参数(web服务函数的参数,即远程端调用的函数的参数)。
第三个hack的地方就是 in.readObject(args[i], ip); 这个方法。 这个方法是我自己加的,原本只有
in.readObject(args[i]); 这个方法。 这个方法就是hessian读取参数值的地方
进去看看
我重载了这个方法,加入了一个String类型的参数,用来把ip地址传进去,并且最后返回这个值。到了这里,hack的原理大家应该知道了--就是强行修改远程调用端的调用函数里边的最后一个参数的值(规定为String类型),把这个值设为我想要的信息,那么服务端的服务函数就会获得这个值,并且进行后续处理。
剩下的步骤就原封不动的是hessian来处理了,没有需要干涉的地方,你也就能在你的服务端service函数里边获得这个你想要的信息了。
这就是Hessian的一个普通流程,不知道分析和Hack的对不对,我在这里是调试成功了,但是还没彻底测试有没有其它bug。 至于跟Spring的结合,待会儿跟帖来说。
(###)
|