SOAP是一种协议,用于交换定义在XML里面的结构化数据,它能够跨越不同的网络协议并在不同的编程模型中使用。SOAP原本是Simple Object Access Protocol(简单对象访问协议)的首字母缩写,但这实际上是一个名不符实的名字,因为这种协议处理的并不是对象,并且时至今日它也已经不再是一种简单的协议了。在最新版的SOAP 1.2规范中,这种协议的官方名称仍然为SOAP,但它已经不再代表Simple Object Access Protocol了。

因为SOAP不仅高度结构化,而且还需要严格地进行定义,所以用于传输数据的XML可能会变得非常复杂。WSDL是客户端与服务器之间的契约,它定义了服务提供的功能以及提供这些功能的方式,服务的每个操作以及输入/输出都需要由WSDL明确地定义。

虽然本章主要关注的是基于REST的Web服务,但出于对比需要,我们也会了解一下基于SOAP的Web服务的运作机制。

SOAP会将它的报文内容放入到信封(envelope)里面,信封相当于一个运输容器,并且它还能够独立于实际的数据传输方式存在。因为本书只会对SOAP Web服务进行考察,所以我们将通过HTTP协议来说明被传输的SOAP报文。

下面是一个经过简化的SOAP请求报文示例:

POST /GetComment HTTP/1.1
Host: www.chitchat.com
Content-Type: application/soap+xml; charset=utf-8

<?xml version="1.0"?>
<soap:Envelope
    xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
    soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
    <soap:Body xmlns:m="http://www.chitchat.com/forum">  
        <m:GetCommentRequest>    
            <m:CommentId>123</m:CommentId>  
        </m:GetCommentRequest >
    </soap:Body>
</soap:Envelope>

因为前面已经介绍过HTTP报文的首部,所以这里给出的HTTP首部对你来说应该不会感到陌生。需要注意的是,Content-Type的值被设置成了application/soap+xml,而HTTP请求的主体就是SOAP报文本身,至于SOAP报文的主体则包含了请求报文。在这个例子中,报文请求的是ID为123的评论:

<m:GetCommentRequest>
    <m:CommentId>123</m:CommentId>
</m:GetCommentRequest >

这条SOAP报文示例经过了简化,实际的SOAP请求通常会复杂得多。下面展示的则是一条简化后的SOAP响应报文示例:

HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8

<?xml version="1.0"?>
<soap:Envelope
    xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
    soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
    <soap:Body xmlns:m="http://www.example.org/stock">  
        <m:GetCommentResponse>    
            <m:Text>Hello World!</m:Text>  
        </m:GetCommentResponse>
    </soap:Body>
</soap:Envelope>

跟请求报文一样,响应报文也被包含在了SOAP报文的主体里面,它的内容为文本“Hello World!”

<m:GetCommentResponse>
    <m:Text>Hello World!</m:Text>  
</m:GetCommentResponse>

正如上面的例子所示,与报文有关的所有数据都会被包含在信封里面。对基于SOAP的Web服务来说,这意味着它传输的所有信息都会被包裹在SOAP信封里面,然后再发送。顺带一提,虽然SOAP 1.2允许通过HTTP的GET方法发送SOAP报文,但大多数基于SOAP的Web服务都是通过HTTP的POST方法发送SOAP报文的。

下面展示的是一个WSDL报文示例,这种报文不仅详细,而且还很冗长,即使对简单的服务来说也是如此。基于SOAP的Web服务之所以没有基于REST的Web服务那么流行,其中一部分原因就与此有关——一个基于SOAP的Web服务越复杂,它对应的WSDL报文就越冗长。

<?xml version="1.0" encoding="UTF-8"?>
<definitions  name ="ChitChat"
    targetNamespace="http://www.chitchat.com/forum.wsdl"
    xmlns:tns="http://www.chitchat.com/forum.wsdl"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="http://schemas.xmlsoap.org/wsdl/">

    <message name="GetCommentRequest">    
        <part name="CommentId" type="xsd:string"/>  
    </message>  
    <message name="GetCommentResponse">    
        <part name="Text" type="xsd:string"/>  
    </message>  

    <portType name="GetCommentPortType">    
        <operation name="GetComment">      
            <input message="tns:GetCommentRequest"/>      
            <output message="tns:GetCommentResponse"/>    
        </operation>  
    </portType>  

    <binding name="GetCommentBinding" 
        type="tns:GetCommentPortType">    
        <soap:binding style="rpc"      
            transport="http://schemas.xmlsoap.org/soap/http"/>    
            <operation name="GetComment">      
                <soap:operation soapAction="getComment"/>      
                <input>
                    <soap:body use="literal"/>
                 </input>
                 <output>        
                     <soap:body use="literal"/>      
                 </output>    
             </operation>  
     </binding>  

     <service name="GetCommentService" >    
         <documentation>      
             Returns a comment    
         </documentation>    
         <port name="GetCommentPortType" binding="tns:GetCommentBinding">      
             <soap:address location="http://localhost:8080/GetComment"/>    
         </port>  
     </service>

</definitions>

位于报文开头的是报文对自身的定义,该定义给出了报文各个部分的名字,以及这些部分的类型:

<message name="GetCommentRequest">  
    <part name="CommentId" type="xsd:string"/>
</message>
<message name="GetCommentResponse">  
    <part name="Text" type="xsd:string"/>
</message>

在此之后,报文通过GetComment操作定义了GetComment-PortType端口,该操作的输入报文为GetCommentRequest,而输出报文则为GetCommentResponse

<portType name="GetCommentPortType">  
    <operation name="GetComment">    
        <input message="tns:GetCommentRequest"/>    
        <output message="tns:GetCommentResponse"/>  
    </operation>
</portType>

最后,报文在位置http://localhost:8080/GetComment定义了一个GetCommentService服务,并将它与GetCommentPortType端口以及GetCommentsBinding地址进行绑定:

<service name="GetCommentService" >  
    <documentation>Returns a comment</documentation>  
    <port name="GetCommentPortType" binding="tns:GetCommentBinding">    
        <soap:address location="http://localhost:8080/GetComment"/>  
    </port>
</service>

在实际中,SOAP请求报文通常会由WSDL生成的SOAP客户端负责生成;

同样地,SOAP响应报文通常也是由WSDL生成的SOAP服务器负责生成。

具体语言的客户端(如一个Go SOAP客户端)通常也会由WSDL负责生成,而其他代码则会通过使用这个客户端与服务器进行交互。这样做的结果是,只要WSDL是明确定义的,那么它生成的SOAP客户端通常也会是健壮的;与此同时,这种做法的缺陷是,开发人员每次修改服务器,即使是修改返回值的类型这样微小的修改,客户端都需要重新生成。重复生成客户端的过程通常都是冗长而乏味的,这也解释了为什么SOAP Web服务通常很少会出现大量的修改——因为对大型的SOAP Web服务来说,频繁的修改将是一场噩梦。

本章接下来不会再对基于SOAP的Web服务做进一步的介绍,但我们会学习如何使用Go语言创建以及分析XML。

results matching ""

    No results matching ""