接着说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源码进行剩余操作了