基于 SOAP 的 Web 服務(wù)和 REST 服務(wù)的描述
在本系列的前兩篇文章中,作者系統(tǒng)的介紹了 REST 服務(wù)的核心概念以及 REST 和 SOAP 服務(wù)的實(shí)現(xiàn)機(jī)理。接下來,我們以獲取股價(jià)的 Web 服務(wù)為例,來看看基于 SOAP 的 Web 服務(wù)和 REST 服務(wù)的描述、發(fā)送請求的方式和響應(yīng)的格式的不同。
清單 1 所示是一個(gè)獲取股價(jià)的基于 SOAP 協(xié)議的 Web 服務(wù)。如果不熟悉 WSDL 規(guī)范的朋友請參考文獻(xiàn),我們這里不再詳述。描述文件看起來很復(fù)雜,其實(shí)就是兩個(gè)服務(wù)端點(diǎn),在 service 元素里面描述的兩個(gè):StockQuoteSoap、StockQuoteHttpGet。StockQuoteSoap 說明這個(gè)服務(wù)端點(diǎn)接受 SOAP 協(xié)議的的請求并在 SOAP body 里面返回服務(wù)的結(jié)果。StockQuoteHttpGet 是以 SOAP over HTTP 的方式提供服務(wù)。另外還有對端口類型、綁定、消息、輸入?yún)?shù)、輸出參數(shù)的描述,有點(diǎn)像對一個(gè)函數(shù)簽名的詳細(xì)描述。
清單 1.WSDL 描述的獲取股價(jià)的 Web 服務(wù)
| 				  
 
 <?xml version="1.0" encoding="utf-8"?> 
 <wsdl:definitions xmlns:http="http://schemas./wsdl/http/" 
 xmlns:soap="http://schemas./wsdl/soap/" 
 xmlns:s="http://www./2001/XMLSchema"
  xmlns:soapenc="http://schemas./soap/encoding/" 
  xmlns:tns="http://www.webserviceX.NET/" 
  xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" 
  xmlns:mime="http://schemas./wsdl/mime/" 
  targetNamespace="http://www.webserviceX.NET/"
  xmlns:wsdl="http://schemas./wsdl/"> 
  <wsdl:types> 
    <s:schema elementFormDefault="qualified" 
    targetNamespace="http://www.webserviceX.NET/"> 
      <s:element name="GetQuote"> 
        <s:complexType> 
          <s:sequence> 
            <s:element minOccurs="0" maxOccurs="1" 
            name="symbol" type="s:string" /> 
          </s:sequence> 
        </s:complexType> 
      </s:element> 
      <s:element name="GetQuoteResponse"> 
        <s:complexType> 
          <s:sequence> 
            <s:element minOccurs="0" maxOccurs="1"
             name="GetQuoteResult" type="s:string" /> 
          </s:sequence> 
        </s:complexType> 
      </s:element> 
      <s:element name="string" nillable="true" type="s:string" /> 
    </s:schema> 
  </wsdl:types> 
  <wsdl:message name="GetQuoteSoapIn"> 
    <wsdl:part name="parameters" element="tns:GetQuote" /> 
  </wsdl:message> 
  <wsdl:message name="GetQuoteSoapOut"> 
    <wsdl:part name="parameters" element="tns:GetQuoteResponse" /> 
  </wsdl:message> 
  <wsdl:message name="GetQuoteHttpGetIn"> 
    <wsdl:part name="symbol" type="s:string" /> 
  </wsdl:message> 
  <wsdl:message name="GetQuoteHttpGetOut"> 
    <wsdl:part name="Body" element="tns:string" /> 
  </wsdl:message> 
  <wsdl:portType name="StockQuoteSoap"> 
    <wsdl:operation name="GetQuote"> 
      <documentation xmlns
      ="http://schemas./wsdl/">Get Stock quote for a company Symbol
      </documentation> 
      <wsdl:input message="tns:GetQuoteSoapIn" /> 
      <wsdl:output message="tns:GetQuoteSoapOut" /> 
    </wsdl:operation> 
  </wsdl:portType> 
  <wsdl:portType name="StockQuoteHttpGet"> 
    <wsdl:operation name="GetQuote"> 
      <documentation xmlns
      ="http://schemas./wsdl/">Get Stock quote for a company Symbol
      </documentation> 
      <wsdl:input message="tns:GetQuoteHttpGetIn" /> 
      <wsdl:output message="tns:GetQuoteHttpGetOut" /> 
    </wsdl:operation> 
  </wsdl:portType> 
  <wsdl:binding name="StockQuoteSoap" type="tns:StockQuoteSoap"> 
    <soap:binding transport="http://schemas./soap/http"
     style="document" /> 
    <wsdl:operation name="GetQuote"> 
      <soap:operation soapAction="http://www.webserviceX.NET/GetQuote" 
      style="document" /> 
      <wsdl:input> 
        <soap:body use="literal" /> 
      </wsdl:input> 
      <wsdl:output> 
        <soap:body use="literal" /> 
      </wsdl:output> 
    </wsdl:operation> 
  </wsdl:binding> 
  <wsdl:binding name="StockQuoteHttpGet" type="tns:StockQuoteHttpGet"> 
    <http:binding verb="GET" /> 
    <wsdl:operation name="GetQuote"> 
      <http:operation location="/GetQuote" /> 
      <wsdl:input> 
        <http:urlEncoded /> 
      </wsdl:input> 
      <wsdl:output> 
        <mime:mimeXml part="Body" /> 
      </wsdl:output> 
    </wsdl:operation> 
  </wsdl:binding> 
  <wsdl:service name="StockQuote"> 
    <wsdl:port name="StockQuoteSoap" binding="tns:StockQuoteSoap"> 
      <soap:address location="http://www./stockquote.asmx" /> 
    </wsdl:port> 
    <wsdl:port name="StockQuoteHttpGet" binding="tns:StockQuoteHttpGet"> 
      <http:address location="http://www./stockquote.asmx" /> 
    </wsdl:port> 
    </wsdl:service> 
 </wsdl:definitions> 
 | 
根據(jù)這個(gè)服務(wù)的描述,我們來看一下怎么調(diào)用這個(gè)服務(wù)。清單 2 和清單 3 給出了調(diào)用示例和響應(yīng)示例。根據(jù)描述我們知道,SOAPAction 是 GetQuote,HTTP method 是 GET,這個(gè)服務(wù)的輸入?yún)?shù)是一個(gè) String 類型的股票代碼,如 IBM,參數(shù)名稱是 symbol,服務(wù)的端點(diǎn)是 www./stockquote.asmx。首先如清單 2 所示構(gòu)建 StockQuoteHttpGet 服務(wù)的請求。
清單 2. A SOAP Request 示例
| 				  
 
 GET /stockquote.asmx HTTP/1.1 
 Host: www. 
 Content-Type: text/xml; charset="utf-8"
 Content-Length: nnn 
 SOAPAction= "http://www.webserviceX.NET/GetQuote"
 <?xml version="1.0" encoding="utf-8"?> 
 <soap:Envelope xmlns:xsi="http://www./2001/XMLSchema-instance" 
 xmlns:xsd="http://www./2001/XMLSchema"
  xmlns:soap="http://schemas./soap/envelope/"> 
  <soap:Body> 
    <GetQuote xmlns="http://www.webserviceX.NET/"> 
      <symbol>IBM</symbol> 
    </GetQuote> 
  </soap:Body> 
 </soap:Envelope> 
 | 
清單 3 返回的是按照 SOAP 協(xié)議封裝的調(diào)用響應(yīng),在 SOAP body 里面,GetQuoteResult 里面放置的是調(diào)用結(jié)果,返回的是 XML 表示的 IBM 在調(diào)用時(shí)刻的股價(jià)信息,
清單 3. A SOAP response 示例
| 				  
 
 HTTP/1.1 200 OK 
 Content-Type: text/xml; charset='utf-8'
 Content-Length: nnn 
 <?xml version="1.0" encoding="UTF-8"?> 
 <soap:Envelope xmlns:xsi="http://www./2001/XMLSchema-instance" 
 xmlns:xsd="http://www./2001/XMLSchema" 
 xmlns:soap="http://schemas./soap/envelope/"> 
  <soap:Body> 
    <GetQuoteResponse xmlns="http://www.webserviceX.NET/"> 
      <GetQuoteResult> 
 <StockQuotes> 
 <Stock> 
 <Symbol> IBM </Symbol> 
 <Last> 144.36 </Last> 
 <Date> 11/18/2010 </Date> 
 <Time> 4:00pm </Time> 
 <Change> 0.00 </Change> 
 <Open> N/A </Open> 
 <High> N/A </High> 
 <Low> N/A </Low> 
 <Volume> 0 </Volume> 
 <MktCap> 179.3B </MktCap> 
 <PreviousClose> 144.36 </PreviousClose> 
 <PercentageChange> 0.00% </PercentageChange> 
 <AnnRange> 116.00 - 147.53 </AnnRange> 
 <Earns> 11.001 </Earns> 
 <P-E> 13.12 </P-E> 
 <Name> International Bus </Name> 
 </Stock> 
 </StockQuotes> 
 </GetQuoteResult> 
    </GetQuoteResponse> 
  </soap:Body> 
 </soap:Envelope> 
 | 
從清單 2 和清單 3 可以看出,基于 SOAP 的 Web 服務(wù)把 SOAP 請求和 SOAP 響應(yīng)封裝在 soap Envelope 中,服務(wù)的調(diào)用端需要自己構(gòu)建這個(gè) SOAP 信封,并且需要一定的 code 去做解析工作。一般來說,XML 的解析是一項(xiàng)復(fù)雜度比較高的任務(wù),比較耗時(shí),這將會影響整個(gè)程序的性能。
下面我們來看一下以 REST 服務(wù)的方式怎么提供和清單 1 對應(yīng)的股票查詢的能力。首先我們還是來看一下服務(wù)的描述,如清單 4 所示。
清單 4. 獲取股價(jià)的 REST 服務(wù)的描述
| 				  
 
 <?xml version="1.0" encoding="UTF-8"?> 
 <service xmlns="http://www.ibm.com/rest/description/1.0/"> 
  <title> Stock quote for a company Symbol </title> 
  <template httpMethod="GET" 
 url=" http://www./stockquote.asmx/GetQuote?symbol={symbol}"/> 
  <parameter name="symbol" required="true" 
  defaultValue="IBM" style="template"/> 
 </service> 
 | 
和清單 1 比較,清單 4 顯得特別簡潔明了,語義也特別清楚。這給程序員的處理程序很大的簡化的可能性。清單 5 和清單 6 顯示了獲取股價(jià)的 REST 服務(wù)的調(diào)用。從清單 5 可以看出,請求的發(fā)送非常的簡單,僅僅是一個(gè) HTTP url,而清單 6 顯示的查詢結(jié)果要清單 3 的查詢結(jié)果看起來語義要清楚很多。
清單 5. A REST Request over HTTP 示例
| GET /stockquote.asmx/GetQuote?symbol=IBM HTTP/1.1 Host: www. | 
清單 6. A REST Response over HTTP 示例
| HTTP/1.1 200 OK Date: Fri, 12 Sept 2010 17:15:33 GMT Last-Modified: Fri, 12 Sept 2010 17:15:33 GMT ETag: "2b3f6-a4-5b572640" Accept-Ranges: updated Content-Type: text/xml; charset="utf-8" <StockQuotes> <Stock> <Symbol> IBM </Symbol> <Last> 144.36 </Last> <Date> 11/18/2010 </Date> <Time> 4:00pm </Time> <Change> 0.00 </Change> <Open> N/A </Open> <High> N/A </High> <Low> N/A </Low> <Volume> 0 </Volume> <MktCap> 179.3B </MktCap> <PreviousClose> 144.36 </PreviousClose> <PercentageChange> 0.00% </PercentageChange> <AnnRange> 116.00 - 147.53 </AnnRange> <Earns> 11.001 </Earns> <P-E> 13.12 </P-E> <Name> International Bus </Name> </Stock> </StockQuotes> | 
分析 SOAP 的 Web 服務(wù)和 REST 服務(wù)的關(guān)系
現(xiàn)在你被認(rèn)為已經(jīng)清楚了基于 SOAP 的 Web 服務(wù)和 REST 服務(wù)的描述,以及已經(jīng)會調(diào)用他們。接下來,我們來看一下這兩種服務(wù)的邏輯關(guān)系。
- 
面向方法和面向資源。從清單 1 可以看出,SOAP 服務(wù)是按照面向方法的方法論來設(shè)計(jì)的,需要服務(wù)提供者清楚的給出每個(gè)方法的名稱、輸入?yún)?shù)、輸出詳細(xì)描述、綁定等等,這些又再次封裝在消息 message 中。而從清單 4 中我們可以看出,REST 服務(wù)是面向資源的,服務(wù)提供者只需要告訴用于定位到服務(wù)的 URL template 以及要實(shí)例化這個(gè) template 所有的參數(shù)描述。為了使這個(gè)服務(wù)可以工作,所以這里我們用了 http://www./stockquote.asmx/GetQuote?symbol={symbol},但是更好的 URL 格式應(yīng)該是http://www./stockquote.asmx/Quote?symbol={symbol},也許你已經(jīng)發(fā)現(xiàn)了,兩個(gè) URL 只是 GetQuote 和 Quote 的差別。奧妙就在這。GetQuote 看起來像一個(gè)方法名稱,而 Quote 是一個(gè)名詞,是一個(gè)資源。知道了這個(gè)差別,可以把 SOAP 服務(wù)的輸出作為一種資源,對應(yīng)提供 REST 服務(wù)。
- 參數(shù)對應(yīng)。在 SOAP 描述文件中我們看到調(diào)用一個(gè) SOAP Action 所需要的輸入的詳細(xì)描述。這些參數(shù)是系統(tǒng)提供服務(wù)所要求的必須的輸入。而在 REST 服務(wù)中,用戶看到的就是一個(gè) URL,所以,我們可以把 SOAP Action 的輸入用 query string 的形式放到 REST 服務(wù)的 URL template 中。之所以叫 template,是因?yàn)椴煌妮斎霑?yīng)不同的 URL 示例,也就是說對應(yīng)到不同的資源示例。
知道了兩種服務(wù)間的邏輯關(guān)系,接下來,我們開始用程序把 SOAP 服務(wù)轉(zhuǎn)化成 REST 服務(wù),當(dāng)然,如果系統(tǒng)需要,你也可以把 REST 服務(wù)轉(zhuǎn)成 SOAP 服務(wù)。
SOAP Web 服務(wù)和 REST 服務(wù)的轉(zhuǎn)換
很多種方式,可以把 SOAP 服務(wù)轉(zhuǎn)化成 REST 服務(wù)。最直接的方式,程序員可以自己寫程序,實(shí)現(xiàn)一個(gè) proxy,提供 REST 端點(diǎn),然后通過 proxy 把 REST 請求轉(zhuǎn)發(fā)到 SOAP 端點(diǎn),然后再實(shí)現(xiàn)調(diào)用結(jié)果的處理。這里我們主要介紹用 IBM 的一些產(chǎn)品來實(shí)現(xiàn)轉(zhuǎn)化的方法。IBM WebSphere sMash 和 IBM Mashuphub 都可以實(shí)現(xiàn)這種轉(zhuǎn)化。這里著重介紹用 IBM WebSphere sMash 平臺實(shí)現(xiàn)的方法。使用 IBM Mashuphub 的實(shí)現(xiàn)方式請參考 IBM Mashup Center 初探 : 第二部分。
WebSphere sMash 即以前的 Project Zero, 它為快速簡便地開發(fā)交互式 Web 應(yīng)用程序提供了開發(fā)環(huán)境。這個(gè)項(xiàng)目的目的是為 Web 開發(fā)提供一個(gè)完整的基礎(chǔ)設(shè)施,讓應(yīng)用程序開發(fā)人員可以將注意力集中在業(yè)務(wù)邏輯上?;ㄒ恍r(shí)間瀏覽和熟悉 Project Zero Web 站點(diǎn)。可以加入 Project Zero 社區(qū)、為這個(gè)項(xiàng)目做貢獻(xiàn),或參與論壇,在各個(gè)開發(fā)階段對項(xiàng)目進(jìn)行評價(jià)。本文只要求您的計(jì)算機(jī)上安裝了合適的 Java? Development Kit (JDK)。
- 創(chuàng)建 WebSphere sMash 開發(fā)環(huán)境
遵循下面的步驟,創(chuàng)建 WebSphere sMash 開發(fā)環(huán)境。
Step 1:從 WebSphere sMash 網(wǎng)站下載工具包 zero.zip。
Step 2: 解壓 zero.zip 到任意文件夾。如圖 1 所示。
圖 1. 解壓 zero.zip 到任意文件夾
 
 
			Step3: 設(shè)置環(huán)境變量 ZERO_HOME 和 Path。如圖 2 所示。
圖 2. 設(shè)置環(huán)境變量
 
				  
 
			Step4: 在命令行運(yùn)行 zero resolve 命令。如圖 3 所示。
圖 3. 命令行運(yùn)行 zero resolve 命令
 
 
			Step5: 創(chuàng)建 eclipse 開發(fā)環(huán)境,詳細(xì)步驟請參考 Plug-in for Java and Groovy。
Step6: 創(chuàng)建一個(gè) WebSphere sMash 新項(xiàng)目,取名 SOAP2REST。如圖 4 所示。
圖 4. 創(chuàng)建 zero 項(xiàng)目 SOAP2REST
 
 
			開始轉(zhuǎn)換
sMash 提供了一個(gè)組件叫 zero.connection.soap.REST2SOAPHandler,它封裝了 SOAP 協(xié)議,負(fù)責(zé)構(gòu)建 SOAP header,發(fā)送 SOAP 請求,調(diào)用成功后,它返回 SOAP body。所以程序員只需要做其中很小的一部分工作就可以完成 SOAP 到 REST 的轉(zhuǎn)換。按照下面的步驟完成轉(zhuǎn)化的過程。
Step 1: 在 zero.config 文件里面添加如清單 7 所示的代碼片段,配置轉(zhuǎn)換 hanlder 以及轉(zhuǎn)換的對應(yīng)關(guān)系。
清單 7. 在 zero.config 中申明 REST2SOAPHandler
| 				  
 
/config/connection/destinations += { 
 "http://localhost:9980/cms/*": { 
 "handlers" : [ 
 { "class" : "zero.core.connection.handlers.logger.SimpleJavaLoggerHandler.class" }, 
 { "class" : "zero.connection.soap.REST2SOAPHandler", 
 "config" : { 
   "endpointAddress" : "http://www./stockquote.asmx", 
   "SOAPVersion" : "1.1", 
   "r2sMapping" : [ 
     { 
      "RESTOperation" : "POST", 
      "SOAPBodyTemplate" : "stockquoterequest.gt", 
       "URLMatch" : "/cms/stockquote/{symbol}", 
"SOAPAction": "http://www.webserviceX.NET/GetQuote"
        } 
            
        ] 
          } 
        }, 
{ "class" : "zero.core.connection.handlers.logger.SimpleJavaLoggerHandler.class", 
    "config" : { 
"request" : { "keys" : ["/connection/request/body", "/connection/request/soapHeaders"] }, 
"response" : { "keys" : ["/connection/response/body",
 "/connection/response/soapHeaders"] } 
        } 
      } 
    ] 
  } 
 } 
 | 
在清單 7 中,配置 REST2SOAPHandler 的各種參數(shù),比如 endpointAddress,SOAPVersion,r2sMapping,實(shí)現(xiàn)類等。在 r2sMapping 中,配置 SOAP 和 REST 的對應(yīng)關(guān)系,SOAP 操作由 SOAPAction 屬性指定,相對應(yīng)的 REST 的操作屬性由 RESTOperation 指定;另外需要指定的是 SOAPBodyTemplate,用 gt 格式的文件指定;URLMatch 表明了 SOAP 服務(wù)端點(diǎn)和 REST 服務(wù)端點(diǎn)的對應(yīng)。在清單 8 給出了 stockquoterequest.gt 的內(nèi)容。
清單 8. 指明 SOAP 請求 Header 中的內(nèi)容
| 				  
 
 <% 
 // the SOAP request body 
 %> 
 <GetQuote xmlns="http://www.webserviceX.NET/"> 
      <symbol><%=r2s_getParam("symbol")%></symbol> 
 </GetQuote> 
 | 
其中 r2s_getParam("symbol") 指的是從 REST 請求的 request 里面取出來參數(shù)的值。比如 REST 請求是 http://localhost:9980/stockquote.gt?symbol=IBM,那么 r2s_getParam("symbol") 的值就是 IBM.
指明了 handler 和 SOAP 請求后,我們需要創(chuàng)建一個(gè) public 的 zero resource,在 public 目錄下面,我們把這個(gè) resource 叫做 stockquote.gt 吧,清單 9 給出了具體的內(nèi)容。
清單 9. 聲明一個(gè) zero 的 public 的資源 stockquote.gt
| 				  
 
 <% 
 import zero.core.connection.*; 
 def symbol = java.net.URLEncoder.encode(request.params.symbol[]) 
 logger.INFO{symbol} 
 %> 
 <% 
 try { 
 // 這里的 URL 應(yīng)該和 zero.config 中的 URL 對應(yīng),并指明 REST 的操作為 POST 
 conn = new Connection("http://localhost:9980/cms/stockquote/${symbol}",
   Connection.Operation.POST); 
 // 指明 content-type 
 conn.addRequestHeader("Content-Type", "application/xml"); 
 // 發(fā)送請求
 resp = conn.getResponse(); 
 body = resp.getResponseBodyAsString(); 
 if (body == null) { 
 throw new Exception("Response body incorrect: " + body.toString()); 
 } 
 // 取出服務(wù)返回的相應(yīng)
 def respObj = zero.json.Json.decode(body); 
 request.json.output = respObj; 
 // 指明一個(gè) gt 用來處理返回的相應(yīng),如清單 10 
 request.view = "stockquote.gt"; 
 render() 
 } catch (Exception e) { 
 e.printStackTrace(); 
 print("<p><strong>Test failed!</strong></p><p>
 "+zero.util.XMLEncoder.escapeXML(e.toString())+"</p>"); 
 } 
 %> 
 | 
聲明了 public 的資源后,用戶就可以用 http://localhost:9980/stockquote.gt?symbol=IBM 的方式訪問資源了。
清單 10. 處理返回的相應(yīng)的 groovy 模板
| <% headers.out."Content-Type" = "application/xml" def respObj = request.json.output[] def stockquote = respObj.GetQuoteResponse.GetQuoteResult print(stockquote); %> | 
本文作為 REST 服務(wù)最佳實(shí)踐的第三篇,通過一個(gè)實(shí)際的例子,從兩種不同類型的 Web 服務(wù)的描述入手,輔助于兩種不同技術(shù)實(shí)現(xiàn)的 Web 服務(wù)的調(diào)用實(shí)例,詳細(xì)介紹 SOAP Web 服務(wù)和 REST 服務(wù)的關(guān)系,并示例介紹基于 WebSphere sMash 的 SOAP Web 服務(wù)和 REST 服務(wù)的轉(zhuǎn)換,從而使程序員可以輕松的利用已有系統(tǒng)的功能,快速構(gòu)建 REST 服務(wù)。
學(xué)習(xí)
- IBM developerWorks Web 專區(qū):通過專門關(guān)于 Web 技術(shù)的文章和教程,擴(kuò)展您在網(wǎng)站開發(fā)方面的技能。
- Project Zero、WebSphere sMash 和 PHP 入門:本教程將演示如何輕松掌握 Project Zero 基礎(chǔ),包括開發(fā)工具的安裝
- WebSphere sMash 使用入門:介紹怎么搭建 WebSphere sMash 環(huán)境。
- WSDL specification:WSDL 規(guī)范,介紹 WSDL 語法。
- IBM Mashup Center 初探 : 第二部分, 介紹怎么使用 InfoSphere MashupHub 的 plugin 創(chuàng)建一個(gè) REST 資源。
討論

 
                         
                                
 
                                 平均分 (3個(gè)評分)
 平均分 (3個(gè)評分)


 
                        

