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

来源:互联网 发布:ios存储数据的方式 编辑:程序博客网 时间:2024/06/01 12:35

from: https://www.ibm.com/developerworks/cn/webservices/1102_mace_restservicePart3/1102_mace_restservicePart3.html?ca=drs-

基于 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 服务
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?xmlversion="1.0"encoding="utf-8"?>
<wsdl:definitionsxmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
 xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:tns="http://www.webserviceX.NET/"
 xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
 xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
 targetNamespace="http://www.webserviceX.NET/"
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:types>
   <s:schemaelementFormDefault="qualified"
   targetNamespace="http://www.webserviceX.NET/">
     <s:elementname="GetQuote">
       <s:complexType>
         <s:sequence>
           <s:elementminOccurs="0"maxOccurs="1"
           name="symbol"type="s:string"/>
         </s:sequence>
       </s:complexType>
     </s:element>
     <s:elementname="GetQuoteResponse">
       <s:complexType>
         <s:sequence>
           <s:elementminOccurs="0"maxOccurs="1"
            name="GetQuoteResult"type="s:string"/>
         </s:sequence>
       </s:complexType>
     </s:element>
     <s:elementname="string"nillable="true"type="s:string"/>
   </s:schema>
 </wsdl:types>
 <wsdl:messagename="GetQuoteSoapIn">
   <wsdl:partname="parameters"element="tns:GetQuote"/>
 </wsdl:message>
 <wsdl:messagename="GetQuoteSoapOut">
   <wsdl:partname="parameters"element="tns:GetQuoteResponse"/>
 </wsdl:message>
 <wsdl:messagename="GetQuoteHttpGetIn">
   <wsdl:partname="symbol"type="s:string"/>
 </wsdl:message>
 <wsdl:messagename="GetQuoteHttpGetOut">
   <wsdl:partname="Body"element="tns:string"/>
 </wsdl:message>
 <wsdl:portTypename="StockQuoteSoap">
   <wsdl:operationname="GetQuote">
     <documentationxmlns
     ="http://schemas.xmlsoap.org/wsdl/">Get Stock quote for a company Symbol
     </documentation>
     <wsdl:inputmessage="tns:GetQuoteSoapIn"/>
     <wsdl:outputmessage="tns:GetQuoteSoapOut"/>
   </wsdl:operation>
 </wsdl:portType>
 <wsdl:portTypename="StockQuoteHttpGet">
   <wsdl:operationname="GetQuote">
     <documentationxmlns
     ="http://schemas.xmlsoap.org/wsdl/">Get Stock quote for a company Symbol
     </documentation>
     <wsdl:inputmessage="tns:GetQuoteHttpGetIn"/>
     <wsdl:outputmessage="tns:GetQuoteHttpGetOut"/>
   </wsdl:operation>
 </wsdl:portType>
 <wsdl:bindingname="StockQuoteSoap"type="tns:StockQuoteSoap">
   <soap:bindingtransport="http://schemas.xmlsoap.org/soap/http"
    style="document"/>
   <wsdl:operationname="GetQuote">
     <soap:operationsoapAction="http://www.webserviceX.NET/GetQuote"
     style="document"/>
     <wsdl:input>
       <soap:bodyuse="literal"/>
     </wsdl:input>
     <wsdl:output>
       <soap:bodyuse="literal"/>
     </wsdl:output>
   </wsdl:operation>
 </wsdl:binding>
 <wsdl:bindingname="StockQuoteHttpGet"type="tns:StockQuoteHttpGet">
   <http:bindingverb="GET"/>
   <wsdl:operationname="GetQuote">
     <http:operationlocation="/GetQuote"/>
     <wsdl:input>
       <http:urlEncoded/>
     </wsdl:input>
     <wsdl:output>
       <mime:mimeXmlpart="Body"/>
     </wsdl:output>
   </wsdl:operation>
 </wsdl:binding>
 <wsdl:servicename="StockQuote">
   <wsdl:portname="StockQuoteSoap"binding="tns:StockQuoteSoap">
     <soap:addresslocation="http://www.webservicex.net/stockquote.asmx"/>
   </wsdl:port>
   <wsdl:portname="StockQuoteHttpGet"binding="tns:StockQuoteHttpGet">
     <http:addresslocation="http://www.webservicex.net/stockquote.asmx"/>
   </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

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

清单 2. A SOAP Request 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /stockquote.asmx HTTP/1.1
Host: www.webservicex.net
Content-Type: text/xml; charset="utf-8"
Content-Length: nnn
SOAPAction= "http://www.webserviceX.NET/GetQuote"
 
<?xmlversion="1.0"encoding="utf-8"?>
<soap:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 <soap:Body>
   <GetQuotexmlns="http://www.webserviceX.NET/">
     <symbol>IBM</symbol>
   </GetQuote>
 </soap:Body>
</soap:Envelope>

清单 3 返回的是按照 SOAP 协议封装的调用响应,在 SOAP body 里面,GetQuoteResult 里面放置的是调用结果,返回的是 XML 表示的 IBM 在调用时刻的股价信息,

清单 3. A SOAP response 示例
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
HTTP/1.1 200 OK
Content-Type: text/xml; charset='utf-8'
Content-Length: nnn
 
<?xmlversion="1.0"encoding="UTF-8"?>
<soap:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 <soap:Body>
   <GetQuoteResponsexmlns="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 服务的描述
1
2
3
4
5
6
7
8
<?xmlversion="1.0"encoding="UTF-8"?>
<servicexmlns="http://www.ibm.com/rest/description/1.0/">
 <title> Stock quote for a company Symbol </title>
 <templatehttpMethod="GET"
url=" http://www.webservicex.net/stockquote.asmx/GetQuote?symbol={symbol}"/>
 <parametername="symbol"required="true"
 defaultValue="IBM"style="template"/>
</service>

和清单 1 比较,清单 4 显得特别简洁明了,语义也特别清楚。这给程序员的处理程序很大的简化的可能性。清单 5 和清单 6 显示了获取股价的 REST 服务的调用。从清单 5 可以看出,请求的发送非常的简单,仅仅是一个 HTTP url,而清单 6 显示的查询结果要清单 3 的查询结果看起来语义要清楚很多。

清单 5. A REST Request over HTTP 示例
1
2
GET /stockquote.asmx/GetQuote?symbol=IBM   HTTP/1.1
Host: www.webservicex.net
清单 6. A REST Response over HTTP 示例
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
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.webservicex.net/stockquote.asmx/GetQuote?symbol={symbol},但是更好的 URL 格式应该是http://www.webservicex.net/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. 设置环境变量-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
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
/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.webservicex.net/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 中的内容
1
2
3
4
5
6
<%
// the SOAP request body
%>
<GetQuotexmlns="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
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
<%
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 模板
1
2
3
4
5
6
<%
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 服务。

阅读全文
0 0