随笔-4  评论-1  文章-0  trackbacks-0

项目选定Hessian作为web service的实现方式,确实很轻量级,速度就跟直接用socket差不多,全是二进制传送节约了不少开销。但是在使用过程中有业务需要是必须获得远程端的ip地址,主机名等信息的。翻便Hessian的文档和google了n次未果,迫不得已到caucho和spring论坛去问,都没有得到答复。今天心一横把hessian的源代码加入到项目中单步跟踪,总算有点小收获。献丑分享出来,一方面给需要的朋友,主要还是希望各位找找是否存在bug,以及是否有更好的改良。

一:先撇开Spring不谈,来看看纯Hessian的调用
按照hessian文档里边介绍的demo,在web.xml里边如下配置

 1   <servlet>
 2   <servlet-name>hello</servlet-name>
 3   <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
 4    <init-param>
 5      <param-name>home-class</param-name>
 6      <param-value>example.BasicService</param-value>
 7    </init-param>
 8    <init-param>
 9      <param-name>home-api</param-name>
10      <param-value>example.Basic</param-value>
11    </init-param>
12  </servlet>
13
14  <servlet-mapping>
15    <url-pattern>/hello</url-pattern>
16    <servlet-name>hello</servlet-name>
17  </servlet-mapping>
18
19

 

由此可知Hessian调用的入口是[color=red]HessianServlet[/color]这个Servlet,进去看看

 

  1/**
  2 * Servlet for serving Hessian services.
  3 */

  4public class HessianServlet extends GenericServlet {
  5  private Class _homeAPI;
  6  private Object _homeImpl;
  7  
  8  private Class _objectAPI;
  9  private Object _objectImpl;
 10  
 11  private HessianSkeleton _homeSkeleton;
 12  private HessianSkeleton _objectSkeleton;
 13
 14  private SerializerFactory _serializerFactory;
 15
 16  public String getServletInfo()
 17  {
 18    return "Hessian Servlet";
 19  }

 20
 21  /**
 22   * Sets the home api.
 23   */

 24  public void setHomeAPI(Class api)
 25  {
 26    _homeAPI = api;
 27  }

 28
 29  /**
 30   * Sets the home implementation
 31   */

 32  public void setHome(Object home)
 33  {
 34    _homeImpl = home;
 35  }

 36
 37  /**
 38   * Sets the object api.
 39   */

 40  public void setObjectAPI(Class api)
 41  {
 42    _objectAPI = api;
 43  }

 44
 45  /**
 46   * Sets the object implementation
 47   */

 48  public void setObject(Object object)
 49  {
 50    _objectImpl = object;
 51  }

 52
 53  /**
 54   * Sets the service class.
 55   */

 56  public void setService(Object service)
 57  {
 58    setHome(service);
 59  }

 60
 61  /**
 62   * Sets the api-class.
 63   */

 64  public void setAPIClass(Class api)
 65  {
 66    setHomeAPI(api);
 67  }

 68
 69  /**
 70   * Gets the api-class.
 71   */

 72  public Class getAPIClass()
 73  {
 74    return _homeAPI;
 75  }

 76
 77  /**
 78   * Sets the serializer factory.
 79   */

 80  public void setSerializerFactory(SerializerFactory factory)
 81  {
 82    _serializerFactory = factory;
 83  }

 84
 85  /**
 86   * Gets the serializer factory.
 87   */

 88  public SerializerFactory getSerializerFactory()
 89  {
 90    if (_serializerFactory == null)
 91      _serializerFactory = new SerializerFactory();
 92
 93    return _serializerFactory;
 94  }

 95
 96  /**
 97   * Sets the serializer send collection java type.
 98   */

 99  public void setSendCollectionType(boolean sendType)
100  {
101    getSerializerFactory().setSendCollectionType(sendType);
102  }

103
104  /**
105   * Initialize the service, including the service object.
106   */

107  public void init(ServletConfig config)
108    throws ServletException
109  {
110    super.init(config);
111    
112    try {
113      if (_homeImpl != null{
114      }

115      else if (getInitParameter("home-class"!= null{
116 String className = getInitParameter("home-class");
117 
118 Class homeClass = loadClass(className);
119
120 _homeImpl = homeClass.newInstance();
121
122 init(_homeImpl);
123      }

124      else if (getInitParameter("service-class"!= null{
125 String className = getInitParameter("service-class");
126 
127 Class homeClass = loadClass(className);
128
129 _homeImpl = homeClass.newInstance();
130 
131 init(_homeImpl);
132      }

133      else {
134 if (getClass().equals(HessianServlet.class))
135   throw new ServletException("server must extend HessianServlet");
136
137 _homeImpl = this;
138      }

139
140      if (_homeAPI != null{
141      }

142      else if (getInitParameter("home-api"!= null{
143 String className = getInitParameter("home-api");
144 
145 _homeAPI = loadClass(className);
146      }

147      else if (getInitParameter("api-class"!= null{
148 String className = getInitParameter("api-class");
149
150 _homeAPI = loadClass(className);
151      }

152      else if (_homeImpl != null{
153 _homeAPI = findRemoteAPI(_homeImpl.getClass());
154
155 if (_homeAPI == null)
156   _homeAPI = _homeImpl.getClass();
157      }

158      
159      if (_objectImpl != null{
160      }

161      else if (getInitParameter("object-class"!= null{
162 String className = getInitParameter("object-class");
163 
164 Class objectClass = loadClass(className);
165
166 _objectImpl = objectClass.newInstance();
167
168 init(_objectImpl);
169      }

170
171      if (_objectAPI != null{
172      }

173      else if (getInitParameter("object-api"!= null{
174 String className = getInitParameter("object-api");
175 
176 _objectAPI = loadClass(className);
177      }

178      else if (_objectImpl != null)
179 _objectAPI = _objectImpl.getClass();
180
181      _homeSkeleton = new HessianSkeleton(_homeImpl, _homeAPI);
182      if (_objectAPI != null)
183 _homeSkeleton.setObjectClass(_objectAPI);
184
185      if (_objectImpl != null{
186 _objectSkeleton = new HessianSkeleton(_objectImpl, _objectAPI);
187 _objectSkeleton.setHomeClass(_homeAPI);
188      }

189      else
190 _objectSkeleton = _homeSkeleton;
191    }
 catch (ServletException e) {
192      throw e;
193    }
 catch (Exception e) {
194      throw new ServletException(e);
195    }

196  }

197
198  private Class findRemoteAPI(Class implClass)
199  {
200    if (implClass == null || implClass.equals(GenericService.class))
201      return null;
202    
203    Class []interfaces = implClass.getInterfaces();
204
205    if (interfaces.length == 1)
206      return interfaces[0];
207
208    return findRemoteAPI(implClass.getSuperclass());
209  }

210
211  private Class loadClass(String className)
212    throws ClassNotFoundException
213  {
214    ClassLoader loader = Thread.currentThread().getContextClassLoader();
215
216    if (loader != null)
217      return Class.forName(className, false, loader);
218    else
219      return Class.forName(className);
220  }

221
222  private void init(Object service)
223    throws ServletException
224  {
225    if (service instanceof Service)
226      ((Service) service).init(getServletConfig());
227    else if (service instanceof Servlet)
228      ((Servlet) service).init(getServletConfig());
229  }

230  
231  /**
232   * Execute a request.  The path-info of the request selects the bean.
233   * Once the bean's selected, it will be applied.
234   */

235  public void service(ServletRequest request, ServletResponse response)
236    throws IOException, ServletException
237  {
238    HttpServletRequest req = (HttpServletRequest) request;
239    HttpServletResponse res = (HttpServletResponse) response;
240
241    if (! req.getMethod().equals("POST")) {
242      res.setStatus(500"Hessian Requires POST");
243      PrintWriter out = res.getWriter();
244
245      res.setContentType("text/html");
246      out.println("<h1>Hessian Requires POST</h1>");
247      
248      return;
249    }

250
251    String serviceId = req.getPathInfo();
252    String objectId = req.getParameter("id");
253    if (objectId == null)
254      objectId = req.getParameter("ejbid");
255
256    ServiceContext.begin(req, serviceId, objectId);
257
258    try {
259      InputStream is = request.getInputStream();
260      OutputStream os = response.getOutputStream();
261
262      Hessian2Input in = new Hessian2Input(is);
263      AbstractHessianOutput out;
264
265      SerializerFactory serializerFactory = getSerializerFactory();
266      
267      in.setSerializerFactory(serializerFactory);
268
269      int code = in.read();
270
271      if (code != 'c'{
272 // XXX: deflate
273 throw new IOException("expected 'c' in hessian input at " + code);
274      }

275
276      int major = in.read();
277      int minor = in.read();
278
279      if (major >= 2)
280 out = new Hessian2Output(os);
281      else
282 out = new HessianOutput(os);
283      
284      out.setSerializerFactory(serializerFactory);
285
286      if (objectId != null)
287 _objectSkeleton.invoke(in, out);
288      else
289 _homeSkeleton.invoke(in, out);
290
291      out.close();
292    }
 catch (RuntimeException e) {
293      throw e;
294    }
 catch (ServletException e) {
295      throw e;
296    }
 catch (Throwable e) {
297      throw new ServletException(e);
298    }
 finally {
299      ServiceContext.end();
300    }

301  }

302}

303
304

 

先看init()函数,功能还是一样,初始话一些东西,读入init-param的内容,并且load这些init-param的class

主要的还是service()函数
在service函数里边会获得request和response对象的输入和输出流,用来构造Hessian2Input和Hessian2Output,Hessian就是解析这两个东西来执行函数调用的。当然,在service里边还有一个重要的语句

1ServiceContext.begin(req, serviceId, objectId);


这个函数有点奇怪,我每次到这里serviceId和objectId都是空,不知道是不是历史遗留问题还存在这两个参数。
进去这个类看看

  1public class ServiceContext {
  2  private static final ThreadLocal _localContext = new ThreadLocal();
  3
  4  private ServletRequest _request;
  5  private String _serviceName;
  6  private String _objectId;
  7  private int _count;
  8  private HashMap _headers = new HashMap();
  9
 10  private ServiceContext()
 11  {
 12  }

 13  
 14  /**
 15   * Sets the request object prior to calling the service's method.
 16   *
 17   * @param request the calling servlet request
 18   * @param serviceId the service identifier
 19   * @param objectId the object identifier
 20   */

 21  public static void begin(ServletRequest request,
 22      String serviceName,
 23      String objectId)
 24    throws ServletException
 25  {
 26    ServiceContext context = (ServiceContext) _localContext.get();
 27
 28    if (context == null{
 29      context = new ServiceContext();
 30      _localContext.set(context);
 31    }

 32
 33    context._request = request;
 34    context._serviceName = serviceName;
 35    context._objectId = objectId;
 36    context._count++;
 37  }

 38
 39  /**
 40   * Returns the service request.
 41   */

 42  public static ServiceContext getContext()
 43  {
 44    return (ServiceContext) _localContext.get();
 45  }

 46
 47  /**
 48   * Adds a header.
 49   */

 50  public void addHeader(String header, Object value)
 51  {
 52    _headers.put(header, value);
 53  }

 54
 55  /**
 56   * Gets a header.
 57   */

 58  public Object getHeader(String header)
 59  {
 60    return _headers.get(header);
 61  }

 62
 63  /**
 64   * Gets a header from the context.
 65   */

 66  public static Object getContextHeader(String header)
 67  {
 68    ServiceContext context = (ServiceContext) _localContext.get();
 69
 70    if (context != null)
 71      return context.getHeader(header);
 72    else
 73      return null;
 74  }

 75
 76  /**
 77   * Returns the service request.
 78   */

 79  public static ServletRequest getContextRequest()
 80  {
 81    ServiceContext context = (ServiceContext) _localContext.get();
 82
 83    if (context != null)
 84      return context._request;
 85    else
 86      return null;
 87  }

 88
 89  /**
 90   * Returns the service id, corresponding to the pathInfo of the URL.
 91   */

 92  public static String getContextServiceName()
 93  {
 94    ServiceContext context = (ServiceContext) _localContext.get();
 95
 96    if (context != null)
 97      return context._serviceName;
 98    else
 99      return null;
100  }

101
102  /**
103   * Returns the object id, corresponding to the ?id= of the URL.
104   */

105  public static String getContextObjectId()
106  {
107    ServiceContext context = (ServiceContext) _localContext.get();
108
109    if (context != null)
110      return context._objectId;
111    else
112      return null;
113  }

114
115  /**
116   * Cleanup at the end of a request.
117   */

118  public static void end()
119  {
120    ServiceContext context = (ServiceContext) _localContext.get();
121
122    if (context != null && --context._count == 0{
123      context._request = null;
124
125      context._headers.clear();
126    }

127  }

128
129  /**
130   * Returns the service request.
131   *
132   * @deprecated
133   */

134  public static ServletRequest getRequest()
135  {
136    ServiceContext context = (ServiceContext) _localContext.get();
137
138    if (context != null)
139      return context._request;
140    else
141      return null;
142  }

143
144  /**
145   * Returns the service id, corresponding to the pathInfo of the URL.
146   *
147   * @deprecated
148   */

149  public static String getServiceName()
150  {
151    ServiceContext context = (ServiceContext) _localContext.get();
152
153    if (context != null)
154      return context._serviceName;
155    else
156      return null;
157  }

158
159  /**
160   * Returns the object id, corresponding to the ?id= of the URL.
161   *
162   * @deprecated
163   */

164  public static String getObjectId()
165  {
166    ServiceContext context = (ServiceContext) _localContext.get();
167
168    if (context != null)
169      return context._objectId;
170    else
171      return null;
172  }

173}

 

原来ServiceContext 是用来保存当前调用线程的上下文的,比如request对象等(不知道这个解释对不对)。有了这个东西就太好了,因为里边有request,就有了调用端的一切信息,呵呵。

继续回来看那个Servlet,到了真正调用的时候了,也就是这段代码

 

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

 

跟踪invoke方法看看真面目

 

 1public void invoke(AbstractHessianInput in, AbstractHessianOutput out)
 2    throws Throwable
 3  {
 4    ServiceContext context = ServiceContext.getContext();
 5    
 6    String header;
 7    while ((header = in.readHeader()) != null{
 8      Object value = in.readObject();
 9
10      context.addHeader(header, value);
11    }

12    String ip = context.getContextRequest().getRemoteAddr();
13    String methodName = in.readMethod();
14    Method method = getMethod(methodName);
15
16    if (method != null{
17    }

18    else if ("_hessian_getAttribute".equals(methodName)) {
19      String attrName = in.readString();
20      in.completeCall();
21
22      String value = null;
23
24      if ("java.api.class".equals(attrName))
25 value = getAPIClassName();
26      else if ("java.home.class".equals(attrName))
27 value = getHomeClassName();
28      else if ("java.object.class".equals(attrName))
29 value = getObjectClassName();
30
31      out.startReply();
32
33      out.writeObject(value);
34
35      out.completeReply();
36      return;
37    }

38    else if (method == null{
39      out.startReply();
40      out.writeFault("NoSuchMethodException",
41       "The service has no method named: " + in.getMethod(),
42       null);
43      out.completeReply();
44      return;
45    }

46
47    Class []args = method.getParameterTypes();
48    Object []values = new Object[args.length];
49    
50    //args[0]
51
52    for (int i = 0; i < args.length; i++){
53     if(i == args.length-1){
54      values[i] = in.readObject(args[i], ip);
55     }
else{
56      values[i] = in.readObject(args[i]);
57     }

58     
59    }

60      
61
62    in.completeCall();
63
64    Object result = null;
65    
66    try {
67      result = method.invoke(_service, values);
68    }
 catch (Throwable e) {
69      if (e instanceof InvocationTargetException)
70        e = ((InvocationTargetException) e).getTargetException();
71
72      log.log(Level.WARNING, e.toString(), e);
73      
74      out.startReply();
75      out.writeFault("ServiceException", e.getMessage(), e);
76      out.completeReply();
77      return;
78    }

79
80    out.startReply();
81
82    out.writeObject(result);
83    
84    out.completeReply();
85  }

 

就是在这个方法里边,hessian把包装过的输入输出流当作参数传入并进行解析的,看看这个函数的第一句,正是取得ServiceContext的地方,此时应该就是把刚才Servlet里边保存的上下文取出来使用。
这个时候出现了第一个hack的地方

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

在此处我取得远程的ip地址保存起来。然后在第二个hack的地方

 1Class []args = method.getParameterTypes();
 2    Object []values = new Object[args.length];
 3    
 4    //args[0]
 5
 6    for (int i = 0; i < args.length; i++){
 7     if(i == args.length-1){
 8      values[i] = in.readObject(args[i], ip);
 9     }
else{
10      values[i] = in.readObject(args[i]);
11     }

12     
13    }

14


我用这个ip地址取代最后一个参数(web服务函数的参数,即远程端调用的函数的参数)。
第三个hack的地方就是 in.readObject(args[i], ip); 这个方法。 这个方法是我自己加的,原本只有
in.readObject(args[i]); 这个方法。 这个方法就是hessian读取参数值的地方
进去看看

  1/**
  2   * Reads an object from the input stream with an expected type.
  3   */

  4  public Object readObject(Class cl, String ip)
  5    throws IOException
  6  {
  7    if (cl == null || cl == Object.class)
  8      return readObject();
  9    
 10    int tag = _offset < _length ? (_buffer[_offset++& 0xff) : read();
 11
 12    switch (tag) {
 13    case 'N':
 14      return null;
 15
 16    case 'M':
 17    {
 18      String type = readType();
 19      Deserializer reader;
 20      reader = findSerializerFactory().getObjectDeserializer(type);
 21
 22      if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))
 23        return reader.readMap(this);
 24
 25      reader = findSerializerFactory().getDeserializer(cl);
 26
 27      return reader.readMap(this);
 28    }

 29
 30    case 'O':
 31    {
 32      return readObjectDefinition(cl);
 33    }

 34
 35    case 'o':
 36    {
 37      int ref = readInt();
 38
 39      ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref - 1);
 40
 41      return readObjectInstance(cl, def);
 42    }

 43
 44    case 'V':
 45    {
 46      String type = readType();
 47      int length = readLength();
 48      
 49      Deserializer reader;
 50      reader = findSerializerFactory().getObjectDeserializer(type);
 51      
 52      if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))
 53        return reader.readList(this, length);
 54
 55      reader = findSerializerFactory().getDeserializer(cl);
 56
 57      Object v = reader.readList(this, length);
 58
 59      return v;
 60    }

 61
 62    case 'v':
 63    {
 64      int ref = readInt();
 65      String type = (String) _types.get(ref);
 66      int length = readInt();
 67      
 68      Deserializer reader;
 69      reader = findSerializerFactory().getObjectDeserializer(type);
 70      
 71      if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))
 72        return reader.readLengthList(this, length);
 73
 74      reader = findSerializerFactory().getDeserializer(cl);
 75
 76      Object v = reader.readLengthList(this, length);
 77
 78      return v;
 79    }

 80
 81    case 'R':
 82    {
 83      int ref = parseInt();
 84
 85      return _refs.get(ref);
 86    }

 87
 88    case 'r':
 89    {
 90      String type = readType();
 91      String url = readString();
 92
 93      return resolveRemote(type, url);
 94    }

 95    }

 96
 97    if (tag >= 0)
 98      _offset--;
 99
100    Object value = findSerializerFactory().getDeserializer(cl).readObject(this);
101
102    if(value instanceof String){
103     value = ip;
104    }

105    return value;
106  }

107


我重载了这个方法,加入了一个String类型的参数,用来把ip地址传进去,并且最后返回这个值。到了这里,hack的原理大家应该知道了--就是强行修改远程调用端的调用函数里边的最后一个参数的值(规定为String类型),把这个值设为我想要的信息,那么服务端的服务函数就会获得这个值,并且进行后续处理。
剩下的步骤就原封不动的是hessian来处理了,没有需要干涉的地方,你也就能在你的服务端service函数里边获得这个你想要的信息了。

这就是Hessian的一个普通流程,不知道分析和Hack的对不对,我在这里是调试成功了,但是还没彻底测试有没有其它bug。 至于跟Spring的结合,待会儿跟帖来说。

posted on 2007-08-03 17:30 Caixiaopig 阅读(1326) 评论(0)  编辑  收藏 所属分类: 服务器、容器

只有注册用户登录后才能发表评论。


网站导航: