分享

REST Service 的最佳实践,第 3 部分: 把 SOAP 服务转化为 REST ...

 richsky 2012-04-23

REST Service 的最佳实践,第 3 部分: 把 SOAP 服务转化为 REST 服务

马 春娥, 软件工程师, IBM
赵 晨婷, 软件工程师, IBM

简介: 本系列的前两篇文章详细的介绍了 REST 服务以及怎么把一个已有的数据系统 REST 服务化本文作为 REST 服务最佳实践的第三篇,将详细介绍 SOAP Web 服务和 REST 服务的关系,并示例介绍基于 WebSphere sMash 的 SOAP Web 服务和 REST 服务的转换,从而使程序员可以轻松的利用已有系统的功能,快速构建 REST 服务。

查看本系列更多内容

发布日期: 2011 年 3 月 31 日
级别: 中级
访问情况 : 4397 次浏览
评论: 0 (查看 | 添加评论 - 登录)

平均分 2 星 共 3 个评分 平均分 (3个评分)
为本文评分

基于 SOAP 的 Web 服务和 REST 服务的描述

在本系列的前两篇文章中,作者系统的介绍了 REST 服务的核心概念以及 REST 和 SOAP 服务的实现机理。接下来,我们以获取股价的 Web 服务为例,来看看基于 SOAP 的 Web 服务和 REST 服务的描述、发送请求的方式和响应的格式的不同。

清单 1 所示是一个获取股价的基于 SOAP 协议的 Web 服务。如果不熟悉 WSDL 规范的朋友请参考文献,我们这里不再详述。描述文件看起来很复杂,其实就是两个服务端点,在 service 元素里面描述的两个:StockQuoteSoap、StockQuoteHttpGet。StockQuoteSoap 说明这个服务端点接受 SOAP 协议的的请求并在 SOAP body 里面返回服务的结果。StockQuoteHttpGet 是以 SOAP over HTTP 的方式提供服务。另外还有对端口类型、绑定、消息、输入参数、输出参数的描述,有点像对一个函数签名的详细描述。


清单 1.WSDL 描述的获取股价的 Web 服务
				  
 
 <?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> 

根据这个服务的描述,我们来看一下怎么调用这个服务。清单 2 和清单 3 给出了调用示例和响应示例。根据描述我们知道,SOAPAction 是 GetQuote,HTTP method 是 GET,这个服务的输入参数是一个 String 类型的股票代码,如 IBM,参数名称是 symbol,服务的端点是 www./stockquote.asmx。首先如清单 2 所示构建 StockQuoteHttpGet 服务的请求。


清单 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 协议封装的调用响应,在 SOAP body 里面,GetQuoteResult 里面放置的是调用结果,返回的是 XML 表示的 IBM 在调用时刻的股价信息,


清单 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 服务把 SOAP 请求和 SOAP 响应封装在 soap Envelope 中,服务的调用端需要自己构建这个 SOAP 信封,并且需要一定的 code 去做解析工作。一般来说,XML 的解析是一项复杂度比较高的任务,比较耗时,这将会影响整个程序的性能。

下面我们来看一下以 REST 服务的方式怎么提供和清单 1 对应的股票查询的能力。首先我们还是来看一下服务的描述,如清单 4 所示。


清单 4. 获取股价的 REST 服务的描述
				  
 
 <?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 显示了获取股价的 REST 服务的调用。从清单 5 可以看出,请求的发送非常的简单,仅仅是一个 HTTP url,而清单 6 显示的查询结果要清单 3 的查询结果看起来语义要清楚很多。


清单 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 服务和 REST 服务的关系

现在你被认为已经清楚了基于 SOAP 的 Web 服务和 REST 服务的描述,以及已经会调用他们。接下来,我们来看一下这两种服务的逻辑关系。

  1. 面向方法和面向资源。从清单 1 可以看出,SOAP 服务是按照面向方法的方法论来设计的,需要服务提供者清楚的给出每个方法的名称、输入参数、输出详细描述、绑定等等,这些又再次封装在消息 message 中。而从清单 4 中我们可以看出,REST 服务是面向资源的,服务提供者只需要告诉用于定位到服务的 URL template 以及要实例化这个 template 所有的参数描述。为了使这个服务可以工作,所以这里我们用了 http://www./stockquote.asmx/GetQuote?symbol={symbol},但是更好的 URL 格式应该是 http://www./stockquote.asmx/Quote?symbol={symbol},也许你已经发现了,两个 URL 只是 GetQuote 和 Quote 的差别。奥妙就在这。GetQuote 看起来像一个方法名称,而 Quote 是一个名词,是一个资源。知道了这个差别,可以把 SOAP 服务的输出作为一种资源,对应提供 REST 服务。
  2. 参数对应。在 SOAP 描述文件中我们看到调用一个 SOAP Action 所需要的输入的详细描述。这些参数是系统提供服务所要求的必须的输入。而在 REST 服务中,用户看到的就是一个 URL,所以,我们可以把 SOAP Action 的输入用 query string 的形式放到 REST 服务的 URL template 中。之所以叫 template,是因为不同的输入会对应不同的 URL 示例,也就是说对应到不同的资源示例。

知道了两种服务间的逻辑关系,接下来,我们开始用程序把 SOAP 服务转化成 REST 服务,当然,如果系统需要,你也可以把 REST 服务转成 SOAP 服务。

SOAP Web 服务和 REST 服务的转换

很多种方式,可以把 SOAP 服务转化成 REST 服务。最直接的方式,程序员可以自己写程序,实现一个 proxy,提供 REST 端点,然后通过 proxy 把 REST 请求转发到 SOAP 端点,然后再实现调用结果的处理。这里我们主要介绍用 IBM 的一些产品来实现转化的方法。IBM WebSphere sMash 和 IBM Mashuphub 都可以实现这种转化。这里着重介绍用 IBM WebSphere sMash 平台实现的方法。使用 IBM Mashuphub 的实现方式请参考 IBM Mashup Center 初探 : 第二部分

开始之前

  1. 了解 WebSphere sMash

WebSphere sMash 即以前的 Project Zero, 它为快速简便地开发交互式 Web 应用程序提供了开发环境。这个项目的目的是为 Web 开发提供一个完整的基础设施,让应用程序开发人员可以将注意力集中在业务逻辑上。花一些时间浏览和熟悉  Project Zero Web 站点。可以加入 Project Zero 社区、为这个项目做贡献,或参与论坛,在各个开发阶段对项目进行评价。本文只要求您的计算机上安装了合适的 Java? Development Kit (JDK)。

  1. 创建 WebSphere sMash 开发环境

遵循下面的步骤,创建 WebSphere sMash 开发环境。

Step 1:从 WebSphere sMash 网站下载工具包 zero.zip

Step 2: 解压 zero.zip 到任意文件夹。如图 1 所示。


图 1. 解压 zero.zip 到任意文件夹
图 1. 解压 zero.zip 到任意文件夹

Step3: 设置环境变量 ZERO_HOME 和 Path。如图 2 所示。


图 2. 设置环境变量
图 2. 设置环境变量-1 图 2. 设置环境变量-2

Step4: 在命令行运行 zero resolve 命令。如图 3 所示。


图 3. 命令行运行 zero resolve 命令
图 3. 命令行运行 zero resolve 命令

Step5: 创建 eclipse 开发环境,详细步骤请参考 Plug-in for Java and Groovy

Step6: 创建一个 WebSphere sMash 新项目,取名 SOAP2REST。如图 4 所示。


图 4. 创建 zero 项目 SOAP2REST
图 4. 创建 zero 项目 SOAP2REST

开始转换

sMash 提供了一个组件叫 zero.connection.soap.REST2SOAPHandler,它封装了 SOAP 协议,负责构建 SOAP header,发送 SOAP 请求,调用成功后,它返回 SOAP body。所以程序员只需要做其中很小的一部分工作就可以完成 SOAP 到 REST 的转换。按照下面的步骤完成转化的过程。

Step 1: 在 zero.config 文件里面添加如清单 7 所示的代码片段,配置转换 hanlder 以及转换的对应关系。


清单 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 的各种参数,比如 endpointAddress,SOAPVersion,r2sMapping,实现类等。在 r2sMapping 中,配置 SOAP 和 REST 的对应关系,SOAP 操作由 SOAPAction 属性指定,相对应的 REST 的操作属性由 RESTOperation 指定;另外需要指定的是 SOAPBodyTemplate,用 gt 格式的文件指定;URLMatch 表明了 SOAP 服务端点和 REST 服务端点的对应。在清单 8 给出了 stockquoterequest.gt 的内容。


清单 8. 指明 SOAP 请求 Header 中的内容
				  
 
 <% 
 // the SOAP request body 
 %> 
 <GetQuote xmlns="http://www.webserviceX.NET/"> 
      <symbol><%=r2s_getParam("symbol")%></symbol> 
 </GetQuote> 

其中 r2s_getParam("symbol") 指的是从 REST 请求的 request 里面取出来参数的值。比如 REST 请求是 http://localhost:9980/stockquote.gt?symbol=IBM,那么 r2s_getParam("symbol") 的值就是 IBM.

指明了 handler 和 SOAP 请求后,我们需要创建一个 public 的 zero resource,在 public 目录下面,我们把这个 resource 叫做 stockquote.gt 吧,清单 9 给出了具体的内容。


清单 9. 声明一个 zero 的 public 的资源 stockquote.gt
				  
 
 <% 
 import zero.core.connection.*; 
 def symbol = java.net.URLEncoder.encode(request.params.symbol[]) 
 logger.INFO{symbol} 
 %> 
 <% 
 try { 
 // 这里的 URL 应该和 zero.config 中的 URL 对应,并指明 REST 的操作为 POST 
 conn = new Connection("http://localhost:9980/cms/stockquote/${symbol}",
   Connection.Operation.POST); 

 // 指明 content-type 
 conn.addRequestHeader("Content-Type", "application/xml"); 

 // 发送请求
 resp = conn.getResponse(); 
 body = resp.getResponseBodyAsString(); 

 if (body == null) { 
 throw new Exception("Response body incorrect: " + body.toString()); 
 } 
 // 取出服务返回的相应
 def respObj = zero.json.Json.decode(body); 
 request.json.output = respObj; 
 // 指明一个 gt 用来处理返回的相应,如清单 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. 处理返回的相应的 groovy 模板
				  
 
 <% 
 headers.out."Content-Type" = "application/xml"
 def respObj = request.json.output[] 
 def stockquote = respObj.GetQuoteResponse.GetQuoteResult 
 print(stockquote); 
 %> 

结束语

本文作为 REST 服务最佳实践的第三篇,通过一个实际的例子,从两种不同类型的 Web 服务的描述入手,辅助于两种不同技术实现的 Web 服务的调用实例,详细介绍 SOAP Web 服务和 REST 服务的关系,并示例介绍基于 WebSphere sMash 的 SOAP Web 服务和 REST 服务的转换,从而使程序员可以轻松的利用已有系统的功能,快速构建 REST 服务。


参考资料

学习

讨论

作者简介

马春娥,工作在 IBM CSDL Web 2.0 team,开发人员,曾参与 Project Zero 和 Lotus Mashup Center 的开发。主要的关注点在 Web 2.0 领域的数据的建模,数据的处理,数据的可视化,Web 2.0 领域的数据的语义,数据的关联等。

赵晨婷,现就职于 IBM 中国软件开发中心 Web 2.0 开发小组,对 SOA,J2EE,Web 2.0 应用的开发有丰富的经验。主要关注点在数据处理,数据搜索,推荐算法和推荐系统设计等。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多