浏览 9666 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-08-03
一:先撇开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,进去看看 /** * Servlet for serving Hessian services. */ 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"; } /** * Sets the home api. */ public void setHomeAPI(Class api) { _homeAPI = api; } /** * Sets the home implementation */ public void setHome(Object home) { _homeImpl = home; } /** * Sets the object api. */ public void setObjectAPI(Class api) { _objectAPI = api; } /** * Sets the object implementation */ public void setObject(Object object) { _objectImpl = object; } /** * Sets the service class. */ public void setService(Object service) { setHome(service); } /** * Sets the api-class. */ public void setAPIClass(Class api) { setHomeAPI(api); } /** * Gets the api-class. */ public Class getAPIClass() { return _homeAPI; } /** * Sets the serializer factory. */ public void setSerializerFactory(SerializerFactory factory) { _serializerFactory = factory; } /** * Gets the serializer factory. */ public SerializerFactory getSerializerFactory() { if (_serializerFactory == null) _serializerFactory = new SerializerFactory(); return _serializerFactory; } /** * Sets the serializer send collection java type. */ public void setSendCollectionType(boolean sendType) { getSerializerFactory().setSendCollectionType(sendType); } /** * Initialize the service, including the service object. */ 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()); } /** * Execute a request. The path-info of the request selects the bean. * Once the bean's selected, it will be applied. */ 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') { // XXX: deflate 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都是空,不知道是不是历史遗留问题还存在这两个参数。 进去这个类看看 public class ServiceContext { private static final ThreadLocal _localContext = new ThreadLocal(); private ServletRequest _request; private String _serviceName; private String _objectId; private int _count; private HashMap _headers = new HashMap(); private ServiceContext() { } /** * Sets the request object prior to calling the service's method. * * @param request the calling servlet request * @param serviceId the service identifier * @param objectId the object identifier */ public static void begin(ServletRequest request, String serviceName, String objectId) throws ServletException { ServiceContext context = (ServiceContext) _localContext.get(); if (context == null) { context = new ServiceContext(); _localContext.set(context); } context._request = request; context._serviceName = serviceName; context._objectId = objectId; context._count++; } /** * Returns the service request. */ public static ServiceContext getContext() { return (ServiceContext) _localContext.get(); } /** * Adds a header. */ public void addHeader(String header, Object value) { _headers.put(header, value); } /** * Gets a header. */ public Object getHeader(String header) { return _headers.get(header); } /** * Gets a header from the context. */ public static Object getContextHeader(String header) { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context.getHeader(header); else return null; } /** * Returns the service request. */ public static ServletRequest getContextRequest() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context._request; else return null; } /** * Returns the service id, corresponding to the pathInfo of the URL. */ public static String getContextServiceName() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context._serviceName; else return null; } /** * Returns the object id, corresponding to the ?id= of the URL. */ public static String getContextObjectId() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context._objectId; else return null; } /** * Cleanup at the end of a request. */ public static void end() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null && --context._count == 0) { context._request = null; context._headers.clear(); } } /** * Returns the service request. * * @deprecated */ public static ServletRequest getRequest() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context._request; else return null; } /** * Returns the service id, corresponding to the pathInfo of the URL. * * @deprecated */ public static String getServiceName() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context._serviceName; else return null; } /** * Returns the object id, corresponding to the ?id= of the URL. * * @deprecated */ public static String getObjectId() { ServiceContext context = (ServiceContext) _localContext.get(); if (context != null) return context._objectId; else return null; } } 原来ServiceContext 是用来保存当前调用线程的上下文的,比如request对象等(不知道这个解释对不对)。有了这个东西就太好了,因为里边有request,就有了调用端的一切信息,呵呵。 继续回来看那个Servlet,到了真正调用的时候了,也就是这段代码 if (objectId != null) _objectSkeleton.invoke(in, out); else _homeSkeleton.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)) value = getAPIClassName(); else if ("java.home.class".equals(attrName)) value = getHomeClassName(); else if ("java.object.class".equals(attrName)) value = 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]; //args[0] 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]; //args[0] 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读取参数值的地方 进去看看 /** * Reads an object from the input stream with an expected type. */ public Object readObject(Class cl, String ip) throws IOException { if (cl == null || cl == Object.class) return readObject(); int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read(); switch (tag) { case 'N': return null; case 'M': { String type = readType(); Deserializer reader; reader = findSerializerFactory().getObjectDeserializer(type); if (cl != reader.getType() && cl.isAssignableFrom(reader.getType())) return reader.readMap(this); reader = findSerializerFactory().getDeserializer(cl); return reader.readMap(this); } case 'O': { return readObjectDefinition(cl); } case 'o': { int ref = readInt(); ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref - 1); return readObjectInstance(cl, def); } case 'V': { String type = readType(); int length = readLength(); Deserializer reader; reader = findSerializerFactory().getObjectDeserializer(type); if (cl != reader.getType() && cl.isAssignableFrom(reader.getType())) return reader.readList(this, length); reader = findSerializerFactory().getDeserializer(cl); Object v = reader.readList(this, length); return v; } case 'v': { int ref = readInt(); String type = (String) _types.get(ref); int length = readInt(); Deserializer reader; reader = findSerializerFactory().getObjectDeserializer(type); if (cl != reader.getType() && cl.isAssignableFrom(reader.getType())) return reader.readLengthList(this, length); reader = findSerializerFactory().getDeserializer(cl); Object v = reader.readLengthList(this, length); return v; } case 'R': { int ref = parseInt(); return _refs.get(ref); } case 'r': { String type = readType(); String url = readString(); return resolveRemote(type, url); } } if (tag >= 0) _offset--; Object value = findSerializerFactory().getDeserializer(cl).readObject(this); if(value instanceof String){ value = ip; } return value; } 我重载了这个方法,加入了一个String类型的参数,用来把ip地址传进去,并且最后返回这个值。到了这里,hack的原理大家应该知道了--就是强行修改远程调用端的调用函数里边的最后一个参数的值(规定为String类型),把这个值设为我想要的信息,那么服务端的服务函数就会获得这个值,并且进行后续处理。 剩下的步骤就原封不动的是hessian来处理了,没有需要干涉的地方,你也就能在你的服务端service函数里边获得这个你想要的信息了。 这就是Hessian的一个普通流程,不知道分析和Hack的对不对,我在这里是调试成功了,但是还没彻底测试有没有其它bug。 至于跟Spring的结合,待会儿跟帖来说。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-08-03
接着说Spring包装过的Hessian怎么来Hack。
刚开始我以为只要改了hessian的源码,就可以了。其实不然,因为Spring通过了几层包装,让你不能得不到request对象。 Spring里边hessian的入口是HessianServiceExporter这个类,主要方法有两个 public void prepare() { HessianSkeleton skeleton = null; try { try { // Try Hessian 3.x (with service interface argument). Constructor ctor = HessianSkeleton.class.getConstructor(new Class[] {Object.class, Class.class}); checkService(); checkServiceInterface(); skeleton = (HessianSkeleton) ctor.newInstance(new Object[] {getProxyForService(), getServiceInterface()}); } catch (NoSuchMethodException ex) { // Fall back to Hessian 2.x (without service interface argument). Constructor ctor = HessianSkeleton.class.getConstructor(new Class[] {Object.class}); skeleton = (HessianSkeleton) ctor.newInstance(new Object[] {getProxyForService()}); } } catch (Throwable ex) { throw new BeanInitializationException("Hessian skeleton initialization failed", ex); } if (hessian2Available) { // Hessian 2 (version 3.0.20+). this.skeletonInvoker = new Hessian2SkeletonInvoker(skeleton, this.serializerFactory); } else { // Hessian 1 (version 3.0.19-). this.skeletonInvoker = new Hessian1SkeletonInvoker(skeleton, this.serializerFactory); } } /** * Processes the incoming Hessian request and creates a Hessian response. */ public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Assert.notNull(this.skeletonInvoker, "HessianServiceExporter has not been initialized"); if (!"POST".equals(request.getMethod())) { throw new HttpRequestMethodNotSupportedException( "POST", "HessianServiceExporter only supports POST requests"); } try { this.skeletonInvoker.invoke(request.getInputStream(), response.getOutputStream()); } catch (Throwable ex) { throw new NestedServletException("Hessian skeleton invocation failed", ex); }finally{ ServiceContext.end(); } } prepare()方法是在Spring环境加载的时候执行的,用来初始化Hessian的HessianSkeleton并包装成Spring自己的Hessian2SkeletonInvoker。 handleRequest()方法是每次远程调用的入口,里边的这个方法执行后续hessian的解析流程 this.skeletonInvoker.invoke(request.getInputStream(), response.getOutputStream()); skeletonInvoker也就是prepare里初始化的Hessian2SkeletonInvoker,跟进去看看先 class Hessian2SkeletonInvoker extends HessianSkeletonInvoker { public Hessian2SkeletonInvoker(HessianSkeleton skeleton, SerializerFactory serializerFactory) { super(skeleton, serializerFactory); } public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable { Hessian2Input in = new Hessian2Input(inputStream); if (this.serializerFactory != null) { in.setSerializerFactory(this.serializerFactory); } int code = in.read(); if (code != 'c') { throw new IOException("expected 'c' in hessian input at " + code); } AbstractHessianOutput out = null; int major = in.read(); int minor = in.read(); if (major >= 2) { out = new Hessian2Output(outputStream); } else { out = new HessianOutput(outputStream); } if (this.serializerFactory != null) { out.setSerializerFactory(this.serializerFactory); } this.skeleton.invoke(in, out); } } Hessian2SkeletonInvoker的构造函数进行父类的初始化,也就是初始化了最重要的HessianSkeleton 再看invoke()方法,是不是有点眼熟,对了,这就是hessian源代码里边的以部分代码,用来包装输入输入流为Hessian自己的Hessian2Input 等,然后判断开头的字节是否正确,以及确定HessianOutput的版本。这些都是解析输入流里边的字节标志位来实现的。 在方法的最后调用了 this.skeleton.invoke(in, out);,也就是把控制权交给了Hessian来处理,这样后续的工作就是Hessian中完成。但是这样就ok了吗?慢着,我们是不是漏了点什么? 对,那个ServiceContext上下文。因为Hessian是在自己的入口Servlet中生成上下文的,但是Spring并没有生成这个东西。所以,我在这里自己生成了它,唯一一个能够生成的地方就在Spring的HessianServiceExporter的handleRequest()里边,只有在这里才有request对象。因此我把handleRequest()方法中加了一句ServiceContext.begin(request, null, null);然后在finally里边加了一句ServiceContext.end(); 代码如下: public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Assert.notNull(this.skeletonInvoker, "HessianServiceExporter has not been initialized"); ServiceContext.begin(request, null, null); if (!"POST".equals(request.getMethod())) { throw new HttpRequestMethodNotSupportedException( "POST", "HessianServiceExporter only supports POST requests"); } try { this.skeletonInvoker.invoke(request.getInputStream(), response.getOutputStream()); } catch (Throwable ex) { throw new NestedServletException("Hessian skeleton invocation failed", ex); }finally{ ServiceContext.end(); } } ServiceContext.begin()方法生成一个ThreadLocal对象,ServiceContext.end()则释放它里边的上下文。 加了这个地方,就可以用上面的hessian源码进行剩余操作了 现在有疑问的也就是这个地方,加了之后会不会有其它的bug出现。请大家指正 |
|
返回顶楼 | |
发表时间:2007-08-06
在框架里面的class,一般都是受限类(Callback),也就是说,它们的lifecycle是受框架本身控制的。所以,如果你明白了这个框架最高Commander,那么你的hack就几乎可以保证成功了,这是原则。
譬如最常见的Struts吧,弄清ActionServlet,RequestProccessor和Action的关系。当然,Struts本身又在一个更高的框架(Servlet容器)里面,它们的衔接点就是ActionServlet和Servlet Container之间的关系:Servlet Container决定了ActionServlet的lifeCycle。那么,我们该从哪儿hack起? 其实,哪个框架的hack,莫不如此。 如果是做局部的hack,像lz,只要不对原始的process有太大的侵入,应该问题不大(这句话不是废话吗,呵呵),像lz,主要是get information。没有像常用AOP,譬如Spring对Hibernate Session(SessionHolder),那么大的侵入。 |
|
返回顶楼 | |
发表时间:2007-08-07
参考这个帖子的13楼和15楼
http://www.iteye.com/topic/82492?page=2 |
|
返回顶楼 | |
发表时间:2007-08-24
Readonly 写道 参考这个帖子的13楼和15楼
http://www.iteye.com/topic/82492?page=2 这个解决方案也很好,但是只是适合java的client吧 我的项目中是C++的Client,直接按照hessian协议来构造http header和body传到java server的 |
|
返回顶楼 | |