简单对象访问协议(Simple Object Access Protocol,SOAP)是一项发展中的 W3C 标准。它由 IBM、Microsoft、DevelopMentor以及 UserLand Software 开发,用于在网络上进行信息交换。SOAP 位于远程过程调用(Remote Procedure Call,RPC)、XML以及 Web 应用程序这三项技术的交叉点。这种组合十分新,因此开发人员仍旧在学习最高效的部署方法。在大多数第一代应用程序中,SOAP 取代了其他RPC 标准,如 RMI 或 CORBA。不过正如这篇技巧文章中阐明的那样,SOAP 可以做更多的事情。
企业内部网与 Internet 服务谁开发客户机和服务器,这个问题取决于这些程序是部署在企业内部网还是 Internet 上。在企业内部网中,开发客户机和服务器的人员要么属于同一个工作组,要么属于紧密相关的若干工作组。这种情况转化为客户机和服务器之间十分紧密的联系。
当程序部署到 Internet 上时,情况就不同了。客户机和服务器通常根本就是由不同的公司开发的。这样造成的结果是,两方面不会有很多机会来同步各自的工作。还有更糟糕的情况,如果某项服务很成功,那么很多客户端和服务器就会纷纷实现这项服务。
让我们举个例子。想想 Google 搜索引擎(参阅 ),这项服务使很多与之接口的应用程序从中获益。例如,我使用某个XML 编辑器编写我自己的文章。当我研究某项主题的时候,我发现可以很方便地从编辑器内部直接进行搜索。发电子邮件时情况类似——通常我们可以从某个公用的Web 站点上搜索通讯簿。这种搜索经常也是从 Google 开始的,因此我的地址簿也因为实现了 Google 服务而受益。那么照片编辑器的情况又如何呢?我们经常会搜索关于某一主题的背景信息,或是搜索一些新技术。因此照片编辑器也可以与Google 接口。上面三种就是 Google 服务已经可以实现的客户机。Google 并不是惟一的搜索引擎,因此您也可能有多种服务器实现。
由于客户机和服务器是分别开发的,服务接口的定义就相当重要了。服务接口和 API 类似,是两个团队相互协作的基础。它定义了方法及调用参数,明确了客户机与服务器各自的职责。这个接口必须是灵活的、模块化的和可扩展的。
为了鼓励人们实现模块化和可扩展性,SOAP 将其请求组织为两个部分:头部(header)与主体(body)。头部通常用于提供请求的技术参数,如会话管理、验证和事务等,而主体部分包含实际的请求。
处理程序为了简化问题,我直接用服务器本身来处理头部。AXIS 提供了一种更为灵活的选择,即处理程序。处理程序可以根据服务器的动作处理特定的头部信息,两种方法的代码十分相似。
AXIS 中的头元素为了解释如何实现头部扩展,我将使用 AXIS(Apache SOAP 工具箱)。给出的示例服务功能很简单:将两个整数相加。服务接口仅仅包含一个方法 add() ,它有两个整型参数。
假设服务器可以对请求进行优先级排序,高优先级的请求响应速度比低优先级请求快。那么您将如何扩展服务接口呢?尽管您可以给 add() 方法扩展出第三个 priority 参数,但是这并不是什么好主意,主要是因为这样做会导致已有的客户机和服务器崩溃。除此之外,引入一个技术参数会污染接口。并不是每一个服务器都能处理优先级,也并不是每一个客户机都具有优先级的概念。更好的方法是将技术参数移到头部,那里才是它该去的地方。
清单 1 是一个可以识别 priority 头部元素的 SOAP 服务器。这个服务用 MessageContext 对象获取一个 Message 对象的实例,用来代表请求。接下来,服务会在头部中搜索 priority 元素。然后,服务会休眠一段时间,休眠的长短有赖于优先级(在这个例子中,我们用休眠的方式来模拟低优先级的请求)。请注意,这项服务可以接收不具有优先级信息的请求。最后,服务器设置 processed 标记,这样 AXIS 就知道这个元素已经处理过了(如果元素具有 mustUnderstand 属性,那么这个标记就是必需的)。这个服务器也可以兼容那些没有实现优先级机制的客户机。
清单 1. 一个可以处理头部扩展的服务器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| import java.util.*;
import org.apache.axis.*;
import org.apache.axis.message.*;
public class HeaderService
{
public int add(int op1, int op2)
throws Exception
{
MessageContext context = MessageContext.getCurrentContext();
Message message = context.getRequestMessage();
SOAPEnvelope envelope = message.getSOAPEnvelope();
SOAPHeaderElement element =
envelope.getHeaderByName("http://psol.com/2003/tips/header",
"priority");
if(element != null)
{
Integer priority =
(Integer)element.getValueAsType(Constants.XSD_INT);
// lower priority requests are delayed to simulate priorities
switch(priority.intValue())
{
case 0:
Thread.sleep(40000);
break;
case 1:
Thread.sleep(30000);
break;
case 2:
Thread.sleep(20000);
break;
case 3:
Thread.sleep(10000);
break;
case 4:
Thread.sleep(5000);
break;
default:
// top speed, no sleeping
break;
}
element.setProcessed(true);
}
else
// unless the service requests a priority, it runs very low
Thread.sleep(50000);
return op1 + op2;
}
}
|
清单 2 是客户机的代码。如果从命令行中输入了优先级参数,那么客户机将创建一个 SOAPHeaderElement 对象并传递给 Call 对象。同样,这个客户机也可以兼容没有实现优先级机制的服务器。
清单 2. 对应的客户机1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| import java.net.*;
import org.apache.axis.client.*;
import org.apache.axis.message.*;
import org.apache.axis.encoding.*;
import javax.xml.rpc.ParameterMode;
public class HeaderClient
{
public static void main(String [] args)
{
if(args.length < 2)
{
System.out.println("HeaderClient op1 op2 [-priority:0-5]");
return;
}
try
{
Integer op1 = new Integer(args[0]),
op2 = new Integer(args[1]);
URL endpoint =
new URL("http://localhost:8080/axis/HeaderService.jws");
Service service = new Service();
Call call = (Call)service.createCall();
call.setTargetEndpointAddress(endpoint);
call.setOperationName("add");
call.addParameter("op1",XMLType.XSD_INT,ParameterMode.IN);
call.addParameter("op2",XMLType.XSD_INT,ParameterMode.IN);
call.setReturnType(XMLType.XSD_INT);
if(args.length > 2 && args[2].startsWith("-priority:"))
{
Integer priority = new Integer(args[2].substring(10));
if(priority.intValue() < 0 || priority.intValue() > 5)
priority = new Integer(3);
SOAPHeaderElement element =
new SOAPHeaderElement("http://psol.com/2003/tips/header",
"priority");
element.setObjectValue(priority);
call.addHeader(element);
System.out.println("Request priority = " + priority);
}
else
System.out.println("No priority set for request");
Integer result = (Integer)call.invoke(new Object[] { op1, op2 });
System.out.println(op1 + " + " + op2 + " = " + result);
}
catch (Exception x)
{
System.err.println(x.toString());
}
}
}
|
结束语SOAP 通过头部元素的方式鼓励人们设计模块化的、可扩展的服务接口。本篇技巧文章阐明了如何利用这种机制,在常规服务接口之上创建更好(响应速度更快)的服务。同时,这项技术也适用于其他的技术参数。 |