项目选定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 阅读(1330)
评论(0) 编辑 收藏 所属分类:
服务器、容器