项目选定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
*/
4
public 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里边还有一个重要的语句
1
ServiceContext.begin(req, serviceId, objectId);
这个函数有点奇怪,我每次到这里serviceId和objectId都是空,不知道是不是历史遗留问题还存在这两个参数。
进去这个类看看
1
public 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方法看看真面目
1
public 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的地方
1
String ip = context.getContextRequest().getRemoteAddr();
在此处我取得远程的ip地址保存起来。然后在第二个hack的地方
1
Class []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 阅读(1334)
评论(0) 编辑 收藏 所属分类:
服务器、容器