



Web服务及其协议栈是对SOA理念最初的一种尝试。它将服务实现为一种可以自包含、自描述及模块化的Web组件,并通过Web进行发布、查找和调用。WSDL、SOAP和UDDI是对Web服务来说最重要的三个协议。WSDL定义了如何描述一个Web服务;SOAP定义了怎样调用并触发一个Web服务;UDDI则定义了如何发布、管理及查找Web服务的描述信息。为了和后面介绍的REST服务区分,本书将使用SOAP的Web服务统称为SOAP服务。
W3C(万维网联盟)对SOAP服务的定义如下。
定义2.1 SOAP服务(SOAP service):一种支持机器间通过网络交互的软件系统。它的接口描述是机器可处理的(通常采用WSDL语言)。服务之间的交互采用SOAP消息,并通过Web相关标准及基于HTTP协议的XML消息交换方式实现交互(Haas et al.2004)。
作为一个新兴的分布式计算平台,SOAP服务除了达成互操作方面的承诺,在可靠通信、安全、事务、管理、编程模型和协作协议等方面都有相应的规范和协议。
SOAP(simple object access protocol)是“简单对象访问协议”的简称,它的初始目的是提供一种基于XML文本的通信协议,以实现DCOM和CORBA之间的互操作。但是,随着Web服务理念和技术的逐渐成熟,SOAP规范的重心很快从对象转移到通用的XML消息处理框架上。这种重心的变化给SOAP这一缩写词的定义带来了一点小问题。SOAP 1.2工作组沿用了SOAP这个名称,但决定不再使用该词的起始全称以免误导开发人员,因此在最新的SOAP 1.2规范中,其正式的定义并不提及“object”一词。
SOAP是一个轻型的分布式计算协议,它允许在一个分布式的环境中交换信息。SOAP没有与硬件平台、操作系统、编程语言或网络硬件平台捆绑。与其他分布式计算系统不同的是,SOAP建立在开放式标准的顶部,如HTTP和XML。SOAP用基于文本的XML协议与分布式系统通信,而非用其他分布式计算协议(如CORBA、RMI和DCOM)使用的二进制格式。这使得SOAP具有跨硬件平台、操作系统、编程语言和网络硬件平台的高度互操作性。SOAP可以在HTTP上传输,HTTP允许它利用已有的基础设施投资,如Web服务器、代理服务器和防火墙。SOAP也可以用其他协议(如SMTP和JMS)进行传输。
SOAP规范的主要目标是简单性和可扩展性。
(1)简单性:SOAP规范定义的消息结构非常简单,除了这个基本消息结构,SOAP没有定义额外的表述结构标准,没有定义自己的编码格式,也没有定义自己的传输协议;而且,SOAP还避免了许多与组件模型有关的复杂功能,如分布式垃圾收集、对象引用、对象激活及消息批处理等。
(2)可扩展性:SOAP使用XML来封装远程调用和交换的数据,SOAP非常具有弹性,因为它可以使用XML来封装所有的数据,并且扩充它的功能和意义,同时能够通过标准的XML分析技术来进行消息解析。
最简单的SOAP消息处理模型如图2-1所示:XML格式的SOAP消息通过某种网络通信协议(如HTTP、SMTP等)在SOAP消息发送方和接收方之间传送。
图2-1 最简单的SOAP消息处理模型
高级SOAP消息处理模型如图2-2所示:SOAP消息从起始发送方发出后,经过多个中间节点到达最终接收方。
图2-2 高级SOAP消息处理模型
SOAP消息中间节点(简称SOAP节点)位于起始发送方和最终接收方之间,截获SOAP消息并进行相应的处理。大体来说,SOAP消息由消息头和消息体构成,中间节点只能对SOAP消息头进行处理和修改,而无权处理和修改SOAP消息体,这点在SOAP1.2规范中有明确定义。
在处理消息时,SOAP节点将会承担一个或多个角色,SOAP节点的角色决定节点如何处理SOAP消息头。当SOAP节点接收到一条消息时,它首先必须确定其自身的角色,其次看自己是否必须处理该消息(由消息头中的mustUnderstand属性决定)。
SOAP消息结构的基本模型如图2-3所示:整个SOAP消息包含在一个信封中,信封内有一个SOAP消息头和一个SOAP消息体,其中SOAP消息头是可选的,消息头和消息体可以包含多个条目,SOAP消息体可以包含SOAP故障。
图2-3 SOAP消息结构基本模型
上述SOAP消息的概念模型如图2-4所示。
对SOAP消息概念的解释如下。
(1)信封(Envelope):信封是SOAP消息的根元素,包含一个可选的SOAP消息头元素和一个必需的SOAP消息体元素。
(2)消息头(Header):消息头是可选元素,其中可以包含多个任意格式的项,比如描述安全性、事务处理、会话状态信息的项。
图2-4 SOAP消息概念模型
(3)消息体(Body):消息体是必需元素,代表实际的消息负载,可以包含多个任意格式的项,这些项可以采用两种XML结构风格,即文档风格或RPC风格,本节后续会详细讨论这两种风格。
(4)故障(Fault):故障是可选元素,用于携带出错信息。故障只能作为消息体元素的直接子元素,并且一个消息体元素只能包含一个故障元素。
下面详细介绍SOAP消息的各组成部分。
SOAP信封是SOAP消息的根元素,SOAP信封包含两个子元素:一个是可选的消息头,另一个是必需的消息体。在SOAP信封元素中,可以通过XML命名空间定义SOAP消息的版本信息,也可以通过XML命名空间定义编码风格。
1)soapEnv:SOAP消息版本
soapEnv命名空间标明了SOAP消息的版本,其中:
SOAP1.1命名空间的URI是:http://schemas.xmlsoap.org/soap/envelope/;
SOAP1.2命名空间的URI是:http://www.w3.org/2003/05/soap-envelope/。
按照SOAP规范,一个SOAP信封必须与上述命名空间中的一个相关联,不遵守上述命名空间声明的SOAP消息会被视为一个版本错误。
2)encodingStyle:编码风格
SOAP消息通常需要定义的另一个命名空间是SOAP消息的编码风格encodingStyle(关于编码风格详见2.1.1.4节)。
SOAP1.1编码风格的URI是:http://schemas.xmlsoap.org/soap/encoding/;
SOAP1.2编码风格的URI是:http://www.w3.org/2003/05/soap-encoding/。
SOAP消息头是可选的。如果SOAP信封中包含消息头元素,那么它必须作为根元素信封的第一个子元素出现。SOAP消息头也是可扩展的,用户可以自行添加一些用于描述安全性和事务处理的数据,Web服务规范WS-Security、WS-Transaction等都是SOAP消息头的扩展规范。
SOAP消息头主要有如下几个属性。
1)role
在SOAP规范中,一条SOAP消息在到达最终目的地之前,可能会通过多个SOAP节点。每个SOAP节点都能接收和处理SOAP消息并将它转发到下一个SOAP节点。通过设定role属性,可以指明由哪个SOAP节点来处理SOAP消息头。如果选择默认的role属性项,则表明SOAP消息头将被定位至最终的SOAP节点。
2)mustUnderstand
该属性指明接收消息的SOAP节点是否必须理解和处理SOAP消息头。如果mustUnderstand=“false”,表明接收消息的SOAP节点可以忽略SOAP消息头;如果mustUnderstand=“true”,表明接收消息的SOAP节点必须理解并处理SOAP消息头,否则必须返回一个SOAP故障。
3)relay
当relay属性值为“true”时,未被处理的SOAP消息头必须被继续发送。
SOAP消息体包含的是SOAP消息的实际负载,因此在SOAP消息中是必需元素。SOAP消息体由SOAP消息的最终接收方接收并处理。
SOAP消息体中的项可以包含一个可选的encodingStyle属性,代表该项的编码风格,如果消息体中的某项为自己定义了编码风格属性,那么该属性将替代(override)在信封中定义的编码风格。
SOAP消息体可以包含任意内容,但SOAP规范定义了两种消息风格供发送者和接收者使用。这两种消息风格分别被称为RPC风格(RPC style)和文档风格(document style)。SOAP消息体可以包含可选的SOAP故障。
RPC风格的消息遵从SOAP标准,封装的是RPC调用的请求和返回消息,对该类消息的主要约束是必须把操作名称作为封装了对操作的调用请求和返回消息负载的根元素名称,如图2-5和代码2-1(RPC风格的调用请求消息)、代码2-2(RPC风格的返回消息)所示。一般RPC风格的消息可以由SOAP工具自动产生。而由文档风格的消息封装的XML文档可以是消息发送方和消息接收方约定的任意格式。
图2-5 RPC风格的调用请求消息和返回消息
SOAP故障用于携带出错信息,SOAP故障只能作为SOAP消息体的直接子元素出现,且至多出现一次。
SOAP故障包含如下几个子元素。
(1)Code(SOAP 1.1规范中为faultCode;SOAP 1.2规范中为Code):Code元素在故障元素中是必需的。Code元素中包含一个必需的Value子元素和一个可选的Subcode子元素。Value元素的可能取值为故障代码;每个Subcode元素是迭代的,它由一个必需的Value子元素和一个可选的Subcode子元素组成。表2-1列出了SOAP 1.2规范中的故障代码及其含义。
表2-1 SOAP 1.2规范中的故障代码及其含义
(2)Reason(SOAP1.1规范中为faultString;SOAP1.2规范中为Reason):Reason元素在故障元素中是必需的,该元素不是为处理程序设定的,而是为故障代码提供可以读懂的故障解释;Reason元素可以存在多个Text子元素。
(3)Node:Node元素在故障元素中也是可选的,它指明了在消息传递路径中,哪个SOAP节点引发了故障。
(4)Role:Role元素在故障元素中是可选的,它指明了引发故障的SOAP节点的角色。
(5)Detail:Detail元素在故障元素中是可选的。在SOAP1.1规范中,如果SOAP消息体元素的内容无法被处理,那么就需要用到Detail元素,也就是说,Detail元素提供了与消息体元素相关的应用程序的故障信息;SOAP 1.2规范则不再区分是消息头故障还是消息体故障。
SOAP信封及SOAP消息体中的元素都可以设定encodingStyle属性,该属性指明了SOAP消息的编码风格,在信封中设定的编码风格作用于整个SOAP消息体,而消息体包含的元素自己设定的编码风格仅作用于自身的范围。现在,SOAP规范并没有限定encodingStyle属性的取值,但常见的是SOAP编码风格和字面(literal)编码风格。所谓字面编码就是没有编码风格(不设定encodingStyle属性),SOAP消息的格式完全由交互双方自行约定。而SOAP编码风格的消息必须遵从SOAP编码风格。
SOAP 1.1规范的第5部分定义了SOAP编码风格。到SOAP 1.2规范时,SOAP编码风格不再是SOAP规范的一部分,而是单独成为一个可选规范。SOAP 1.2编码风格的URI为http://www.w3.org/2003/05/soap-encoding,如果encodingStyle属性被赋予该URI,则表明使用的是SOAP编码风格。
SOAP编码的原理:在SOAP模式的约束下,应用级对象(如Java对象、C#对象)基于SOAP数据模型规则被转换为SOAP编码的XML消息负载。在发送者和接收者不另行约定消息内容模式的情形下,它们可以使用SOAP编码来实现从应用级对象到XML消息文档的自动处理,在转换过程中,程序设计语言的类型被转换为XML Schema。
下面简单介绍一下SOAP编码是如何把程序设计语言的类型转换为XML Schema的。程序设计语言的类型一般可以分为简单类型和复合类型,简单类型是无法包含其他元素的类型。
简单类型都可以采用叶节点元素来表示,如代码2-3所示。
程序设计语言中的枚举类型直接对应XML Schema中的枚举类型。代码2-4给出的是Month枚举类型。
SOAP编码提供了对结构(Structs)和数组(Arrays)两种复合类型的编码。结构允许把不同类型的值混合在一起,每个值用唯一的存储器存储和接收;数组由多个值组成,这些值用一个顺序位置号存储和检索。
1)结构
在结构类型中,允许把不同类型的值混合在一起,但同一结构中的不同组成元素的名称必须是唯一的。SOAP编码把结构映射为XML Schema复合类型,如代码2-5所示。
2)数组
数组采用顺序位置来标识多个值。数组被定义为“soapEnv:Array”类型或从“soapEnv:Array”衍生的类型。数组表示元素值,对元素名没有特别的约束。数组中包含的元素可以是简单类型或复合类型,数组成员值也可以是数组。数组可以单引用或多引用。代码2-6给出的是一个整型数组的片段示例。
代码2-6中数组元素的类型也可以直接通过“soapEnv:arrayType”属性来指明,如代码2-7所示。
SOAP消息可以和各种网络传输协议绑定,如SOAP 1.0规范最初规定的HTTP协议,以及新SOAP规范和主流的SOAP供应商推出的SMTP、FTP、POP3、BEEP、JMS、MSMQ等协议。
由于当前几乎所有的操作系统都支持HTTP,因此虽然HTTP绑定是可选的,几乎所有SOAP实现方案都支持HTTP绑定,SOAP规范中也只对SOAP的HTTP绑定进行了详细说明。
本节介绍如何实现SOAP的HTTP绑定,SOAP会遵守HTTP请求/响应消息交换模式,在HTTP请求中提供SOAP请求参数,并且在HTTP响应中包含SOAP响应参数。
根据SOAP Web方法(SOAP Web method)
的定义,在与HTTP等网络传输协议进行绑定实现SOAP消息传输时,需要指出所使用的SOAP Web方法,如GET、POST等。GET方法通常被用来取得Web上的信息,POST方法则被用来将信息从客户端传送给服务器,然后利用POST方法所传送的信息就会被服务器上的应用程序使用。利用GET方法只能传送特定类型的信息,而利用POST方法则可以传送各种类型的数据。
代码2-8是用HTTP POST方法表示的SOAP请求。
在这个代码中,第一行包含了三个不同的部分:请求的方法(POST)、请求的URI(/OrderStatus),以及使用协议的版本(HTTP/1.1)。
第二行是请求消息被传送的目标服务器的地址。
第三行包含了媒体类型和字符编码类型,text/xml表示负载是纯文字形式的XML。当HTTP信息中包含SOAP数据主体时,HTTP应用程序必须遵循RFC 2376的内容型别text/xml。负载指的是被传送的数据。SOAP要求必须使用text/xml作为媒体类型;SOAP使用utf-8作为字符编码类型。
第四行则指定负载的大小,以位为单位。
第五行的SOAPAction在SOAP 1.2规范中是可选的,用来指定SOAP HTTP要求的目标。如果SOAP消息的接收节点希望在处理SOAP信封元素之前就能了解SOAP消息的一些确切信息,则可以通过HTTP绑定中的SOAPAction来实现。
代码2-9是代码2-8中请求消息的HTTP响应。
代码2-9的第一行包含了状态码(200)及与状态码相关联的信息(OK)。SOAP HTTP完全遵循HTTP状态码的语法,主要包括如下几种状态码。
(1)成功状态码。2XX HTTP成功状态码用来说明SOAP请求消息已经被接收或被成功处理。其中,200 OK表示请求消息被成功接收,并返回一个响应消息,该响应消息不是一个错误,而是一个正常的SOAP响应;202 Accepted表示请求消息被成功接收和处理,但不会返回响应消息。
(2)错误状态码。通常情况下,HTTP使用4XX表示SOAP消息传送中在客户端出现的错误,使用5XX表示在服务器端出现的错误。其中,400 Bad Request表示HTTP请求或SOAP消息中的XML文档格式错误;405 Method Not Allowed表示服务提供者不支持请求消息中选用的Web方法;415 Unsupported Media Type表示服务提供者不支持HTTP POST消息中Content-Type选用的值。500 Internal Server Error表示服务器内部错误,或者SOAP响应消息包含了SOAP故障元素。
(3)3XX Redirection。它表示请求的资源被迁移了,需要使用传回的相关联的Location头字段的URI值重试请求。
近年来,以SOAP服务为代表的Web服务获得蓬勃发展。它倡导了基于服务组合技术的新型应用构造方式,解决了传统分布式系统所面对的难以互操作和系统紧耦合两大难题。但是,以SOAP服务为代表的Web服务在实际应用过程中表现出了协议栈不成熟、过于复杂、轻视部署和运营等缺点。
为了降低Web应用开发的复杂性,提高系统的可伸缩性,REST体系结构风格被提出,并逐步为人们所接受。基于REST体系结构风格来实现SOA,可以弥补SOAP服务的诸多缺点,如保障服务的简单性和易用性、提高应用系统的可伸缩性等。当前,REST体系结构风格已经受到很多厂商的支持,越来越多的厂商基于REST体系结构风格来对外提供服务。
在REST体系结构风格中,一切需要被引用的事物都可以被看成资源(Richardson et al.2007)。资源的类型非常丰富,包括文档、图片、网页、服务及资源集合等,甚至不在互联网上的事物也可以被看作资源,如人等。每个资源都将通过一个URI唯一标识。资源具有一个或多个表述(representation)。表述是一组数据,用来刻画资源的当前状态(Richardson et al.2007)。例如,“销售数据”资源可以具有数值方式的表述,也可以具有图表方式的表述。资源的表述和状态是紧密相关的。对于一个资源来说,它的状态就是存储在服务器的资源信息,通常以表述的形式从服务器发送到客户端。客户端也同样存在状态,它在接收到资源表述后将做出某些改变(如进入一个新的链接以请求新的资源等),从而从一个状态进入另一个状态。采用REST体系结构风格构建的应用从本质上来看就是一个个状态表述在服务器和客户端间转移的过程,这也是REST体系结构风格被称为“表述性状态转移”的主要原因。下面将通过如图2-6所示的示例来理解这一点。
图2-6 表述性状态转移示例
如图2-6所示,客户端通过URL“http://www.airticket.com/CA/CA1365”来请求航班CA1365这一资源。服务器以表述的形式返回这一资源的状态信息。图2-6所示的表述信息是CA1365.html,但需要注意的是,服务器可能会针对一个资源维护多种状态信息,如航班CA1365资源也可以采用XML形式表示。接收到航班CA1365资源的表述后,客户端将进入一个新的状态。此时,客户端可能会根据“航空公司”这一属性的链接“http://www.airchina.com.cn/”来进一步向服务器请求“国航”这一资源的表述信息。当接收到新的资源表述信息后,客户端又将转移到另一个新的状态。也就是说,客户端将根据每个资源的表述信息发生状态转移,这也就是所谓“表述性状态转移”的实质。
此外,REST体系结构风格要求服务器不能维护与客户端相关的任何状态,这通常被称为无状态性。无状态性是REST体系结构风格的一个重要属性。它是指从客户端到服务器的每个请求都必须包含理解该请求所必需的所有信息(Richardson et al.2007)。无状态性能够显著提升系统的可靠性和可伸缩性。例如,无状态性减少了服务器从局部错误中恢复的任务量,因此系统将变得更加可靠;服务器不必在多个请求中保存状态,容易释放资源,这就加强了系统的可伸缩性等。但是,无状态性也有缺点:由于不能将状态数据保存在服务器的共享上下文中,因此增加了在一系列请求中发送重复数据的开销,严重降低了效率。
为了改变SOAP服务协议栈过于复杂的情况,以及提升分布式系统的可伸缩性等,人们开始构建REST体系结构风格的Web服务。这类Web服务一般被称作REST服务,它是一种面向资源的服务,可以通过统一资源标识符来识别、定位及操作各类资源。
REST服务具有很多优点。首先,它简洁、高效,正确使用了成熟的网络协议(如HTTP),充分利用了已有的互联网基础设施。如图2-7所示为REST服务的常见协议栈。从图中可以看出,REST服务使用的协议大多是常见的互联网协议,如安全协议使用了SSL/TLS等;传输协议使用了URI、HTTP及MIME等;数据表示规范多用XML/JSON;聚合规范和服务发布协议分别选用RSS/ATOM和AtomPub等。其次,REST服务实现简单,对客户端实现要求较低,大多数情况下浏览器就足以胜任。最后,REST服务容易使用,降低了消费者使用服务的学习曲线,服务提供者对服务的维护成本也比较低。因此,REST服务获得了较高的支持率,包括Google在内的各大网站都已经开始提供REST服务。
图2-7 REST服务的常见协议栈
然而,REST服务毕竟是一个新兴产物,还有一些不够成熟的地方,有其自身的适用范围。例如,REST服务不像SOAP服务那样有比较严格的实现标准,REST服务的消息格式大多都是私有的,通用性较差。REST服务对安全性和事务性考虑较少,在面向事务等复杂应用中的表现还不令人满意。此外,REST服务也不是在任何场合都适用的。当设计资源型数据服务(如对某个互联网资源的获取和修改等服务)时,采用REST体系结构风格的思想相对直观,但对于其他一些复杂的服务,按REST体系结构风格来设计会有些牵强,因此很多网站采用REST和RPC混用的实现方式。
REST服务的设计和开发过程并不复杂,有文献(Pautasso et al.2009)总结了REST服务设计与开发的几个主要步骤,下面将逐一介绍这些步骤及实现REST服务的相关技巧。
设计人员应该对应用需求进行分析,识别具有什么资源,以及哪些资源需要被暴露为服务。
为每个服务设计一个URI来对服务进行唯一标识,以便客户端程序访问服务。如图2-8所示为URI的一个简单示例。
图2-8 URI的一个简单示例
在为REST服务设计URI时,应该遵循一些基本原则,以保障设计出来的URI是定义良好的且易于理解的。其主要应遵循以下几项基本原则:①URI应保持简短,不宜过长;②URI应具有描述性,URI和其所代表的资源应该具有直觉上的联系,如可以使用/movie来表示一部电影,而不用来表示一个人;③URI应具有一定的结构和模式,如搜索电影的URI如果被设计为/search/movie,那么搜索电视的URI也应被设计为/search/TV,而不是换一个新的结构;④不要对URI本身做出改变,当需要改变时可以选择重定向(redirection)URI;⑤一个资源可以有多个URI,但一个URI仅能指示一个资源。
针对每个URI,设计人员需要明确地定义在其上的GET、POST、PUT、DELETE四种操作的具体含义和行为,以及每个URI应该支持哪些操作。这需要设计人员对这四种操作有比较深刻的理解。
表2-2给出了基于HTTP协议的四种资源操作。下面将进一步区分其中两组容易混淆的操作。
表2-2 REST体系结构风格下的资源操作
1)GET和POST操作
GET操作是只读操作,具有幂等性,即可以被重复执行,但不会改变资源状态。POST操作是读写操作,会改变资源状态,可能会给服务器带来副作用。当执行POST操作时,浏览器通常会进行提示。
2)PUT和POST操作
PUT和POST操作都可以用来创建资源(初始化资源状态),但它们的语法格式和语义是不同的。PUT操作的语法格式是“PUT/resource/{id}”,当它执行成功后将返回“201 Created”,代表新资源创建成功。POST操作的语法格式是“POST/resource”,当它执行成功后将返回“301 Moved Permanently”,代表旧资源已被成功移除。
我们需要给每个会被访问的资源设计它的表述形式。常见的表述形式有HTML、XML和JSON等。JSON(JavaScript object notation)是一种轻量级的数据交换格式,独立于编程语言,易于阅读,同时易于机器解析和生成。代码2-10给出了JSON的一个示例。
另外,需要注意的是,当一个资源有多个表述形式时,可以根据URI的后缀(扩展名)来选择特定的表述形式。如代码2-11所示,Accept语句指出了资源的三种表述形式,然后在GET语句中可以通过不同的文件扩展名来获取相应的资源表述形式。
在设计资源表述的过程中,还需要考虑如何采用超链的形式来建模资源之间的关系。超链的作用是连接不同的资源或表示资源的当前状态可以进入哪些后续状态。超链信息将被包含在资源的表述中。代码2-12给出了一个包含超链信息的资源表述。
根据前面的设计内容,开发具体的REST服务,并将其部署到服务器上。
SOAP服务和REST服务是容易让人混淆的两种实现技术。在实际应用中,选择哪种技术要视实际需求而定。下面将从多个视角对这两种技术进行对比分析,帮助读者对它们的优缺点和适用场景有直观的认识。
基于传统Web服务协议栈发展起来的SOAP服务历时较长,在众多厂商的支持下,已经达到了一个相对成熟的程度。采用不同平台和不同语言实现的Web服务之间通过SOAP消息已经能够很好地实现交互。相对而言,REST服务虽然备受重视,获得比较广泛的支持,但还不够成熟。这主要表现在两个方面:一是REST服务的协议栈不够完善;二是REST服务的实现技术和过程不够规范。
首先,对比一下SOAP服务和REST服务正常运转所依赖的协议栈。如图2-9所示,SOAP服务协议栈比REST服务协议栈更规范、更完善。但是,SOAP服务协议栈中的大部分协议都是重新制定的,开发者使用时需要有一个较长时间的学习过程。相对地,REST服务协议栈还不够完善,缺少规范的服务描述、服务组合及事务性等多个重要协议。但是,REST服务采用的大多是比较成熟的互联网协议,尽可能地重用已经建设好的互联网基础设施,因此REST服务的开发和使用更简单、更方便。
图2-9 SOAP服务协议栈和REST服务协议栈对比
其次,REST服务已逐渐被各大厂商接受,越来越多的厂商在对外发布服务时都采用了REST体系结构风格。但是,REST服务还没有一个规范的实现方式,存在太多的随意性。例如,不同厂商发布的REST服务可能采用不同的消息格式,这使REST服务在互操作性和通用性上远不如SOAP服务。
最后,现在的SOAP服务协议栈和REST服务协议栈出现了相互借鉴与相互融合的趋势。例如,SOAP 1.2规范开始使用GET操作来获取资源,从而保障更好的安全性;WSDL 2.0规范也开始支持对REST服务的描述等。
SOAP服务和REST服务的实现过程有着极大的不同。下面将通过资源寻址、消息格式、操作接口、状态转移等几个实现要素来对两者的实现方式进行对比。
1)资源寻址
REST服务重用了Web上正在使用的资源寻址模型。它要求为每个资源定义一个URI来唯一标识。使用URI能够以一个规范的方式来访问并使用资源。相对而言,SOAP服务的资源寻址模型则是自定义的。SOAP服务中虽然也使用了URI机制,但SOAP服务中的URI不是用来标识资源的,而是用来标识服务的入口点(entrypoints)的,资源的访问地址是在服务内部实现的,对外是不可见的。SOAP服务可以根据实际情况采用自定义的地址分配和访问方式。
2)消息格式
SOAP服务要求服务间传递的消息按照SOAP消息的格式进行封装。针对这种规范的消息,可以提供统一的消息解析和处理机制。相对地,REST服务的消息格式就随意得多,并不要求服务按某种特定的格式进行封装。例如,REST服务的消息格式可以是一个自定义的XML片段,也可以是HTML文档或JSON格式等。
3)操作接口
REST服务强调标准化的和一致的资源操作接口。例如,基于HTTP协议的REST服务采用了GET、POST、PUT和DELETE四个基本操作。这样做的好处是可以以一致的方式来访问用URI标识出来的不同资源。而在SOAP服务中,资源的操作是自定义的。每个服务可以根据需求定义一系列的资源操作集合。这样做的缺点是,用户在使用服务前必须理解操作的语义,而且难以开发一个较为通用的服务客户端。
4)状态转移
在REST服务中,应用的状态转移是被显式定义并维护的。在定义资源的表述时,将采用超链来定义资源之间的关系。客户端程序通过在超链之间跳转来从一个应用状态转移到另一个应用状态。而在SOAP服务中,状态的转移和管理是隐式的。SOAP消息中可以不包括资源之间的关系。客户端程序在接收到SOAP消息后将进入哪个状态,是由预先编码的程序实现的。
1)效率与易用性
REST服务最引人注意的是它的高效及简洁易用的特性。它最大限度地利用了HTTP等网络协议的设计理念。此外,它所倡导的面向资源设计和统一资源操作接口等都有助于规范和简化开发者的设计与实现过程。与之相对的SOAP服务在效率和易用性方面要稍逊一筹。首先,SOAP服务对于承载的消息有专有的数据格式,封装或解析这些消息将会降低程序的执行效率。其次,由于需求的原因,SOAP服务不断扩充协议栈的内容,导致产生的消息格式越来越复杂,学习曲线也越来越高。
2)安全性
SOAP服务采用了WS-Security规范来实现安全控制。这是一种关于如何在Web服务消息上保证完整性和机密性的规范。该规范描述了如何将签名和加密信息封装到SOAP消息中,还定义了如何在消息中加入安全令牌。与之相对的REST服务则没有专门的规范来保障安全性。如果需要保障安全性,那么可以由开发人员在服务承载的数据中加入自定义的安全消息。
3)事务性
SOAP服务考虑了实际应用场景对于事务性的要求。WS-Transaction是建立在SOAP、WSDL等标准上的一个常见的事务规范。它定义了两种类型的服务协作方式:针对个体服务操作的原子事务模型和针对长期运行的业务事务模型。与之相对的REST服务对事务性的考虑较少,因此它不太适合用于具有较高事务性要求的复杂应用场景。
总的来说,REST服务和SOAP服务有各自的优缺点,适用于不同的应用场景。REST服务对于资源型接口来说很合适,同时适用于一些对效率要求较高,但对事务性和安全性要求较低的应用场景。SOAP服务则与之形成互补,它适用于跨平台的、对安全性和事务性有较高要求的场景,如多企业应用系统的集成等。