程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Hessian源碼分析和Hack --讓Hessian攜帶遠程調用端的信息

Hessian源碼分析和Hack --讓Hessian攜帶遠程調用端的信息

編輯:關於JAVA

項目選定Hessian作為web service的實現方式,確實很輕量級,速度就跟直接用socket差不多,全是二進制傳送節約了不少開銷。但是在使用過程中有業務需要是必須獲得遠程端的ip地址,主機名等信息的。翻便Hessian的文檔和google了n次未果,迫不得已到caucho和spring論壇去問,都沒有得到答復。今天心一橫把hessian的源代碼加入到項目中單步跟蹤,總算有點小收獲。獻丑分享出來,一方面給需要的朋友,主要還是希望各位找找是否存在bug,以及是否有更好的改良。

一:先撇開Spring不談,來看看純Hessian的調用

按照hessian文檔裡邊介紹的demo,在web.xml裡邊如下配置

Java代碼

<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,進去看看

Java代碼 

/**
* 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裡邊還有一個重要的語句

Java代碼

ServiceContext.begin(req, serviceId, objectId);

這個函數有點奇怪,我每次到這裡serviceId和objectId都是空,不知道是不是歷史遺留問題還存在這兩個參數。

進去這個類看看

Java代碼

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,到了真正調用的時候了,也就是這段代碼

Java代碼

if (objectId != null)
   _objectSkeleton.invoke(in, out);
    else
   _homeSkeleton.invoke(in, out);

跟蹤invoke方法看看真面目

Java代碼

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的地方

Java代碼

String ip = context.getContextRequest().getRemoteAddr();

在此處我取得遠程的ip地址保存起來。然後在第二個hack的地方

Java代碼

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讀取參數值的地方

進去看看

Java代碼

/**
  * 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的結合,待會兒跟帖來說。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved