http://www.ibm.com/developerworks/cn/webservices/1101_mace_restservicePart1/1101_mace_restservicePart1.html
RESTService 的最佳实践,第 1 部分: 重新解析 RESTService
简介: 自从 Roy Fielding 于 2000年发表那篇经典的论文已有 10余载,人们真正开始认可并使用 REST 已有 4年左右的时间。关于 REST的介绍的文章也有一些。但是笔者在社区里面发现,很多程序员对REST还存在很多概念上的误解和实现上的疑问。本系列文章根据使用和构建REST服务的几年的经验,提炼出了一些心得和最佳实践,分享给大家。本文是这个REST Service最佳实践系列的第一篇。在本文中,笔者会以全新的视角来剖析REST 服务,详细的讲解 REST里面的几个核心概念,流行的架构,进一步帮助读者理解REST。最后讲述一个真正基于 REST架构风格构建的应用,给读者更直观的感觉。
Web 服务是一种面向服务的架构的技术,通过标准的Web协议提供服务,目的是保证不同平台的应用服务可以互操作。根据W3C 的定义,Web 服务(Webservice)应当是一个软件系统,用以支持网络间不同机器的互动操作。网络服务通常是许多应用程序接口(API)所组成的,它们通过网络,例如国际互联网(Internet)的远程服务器端,执行客户所提交服务的请求。流行的或者曾经流行的Web 服务架构有三种:SOAP RPC over HTTP, XML RPC over HTTP,和 REST over HTTP。下面分别简要介绍这三种架构。
简单对象访问协议(SOAP,全写为 Simple Object AccessProtocol)是一种标准化的通讯规范,主要用于 Web服务(web service)中。SOAP的出现是为了简化网页服务器(Web Server)在从 XML数据库中提取数据时,无需花时间去格式化页面,并能够让不同应用程序之间透过HTTP 通讯协定,以 XML 格式互相交换彼此的数据,使其与编程语言、平台和硬件无关。
用一个简单的例子来说明 SOAP 使用过程,一个 SOAP消息可以发送到一个具有 Web Service 功能的 Web站点,例如,一个图书价格信息的数据库,消息的参数中标明这是一个查询消息,此站点将返回一个XML格式的信息,其中包含了查询结果。由于数据是用一种标准化的可分析的结构来传递的,所以可以直接被第三方站点所利用。
SOAP RPC over HTTP,在 HTTP 上传送 SOAP 并不是说 SOAP会覆盖现有的 HTTP 语义,而是 HTTP 上的 SOAP语义会自然的映射到 HTTP 语义。在使用 HTTP作为协议绑定的场合中, RPC 请求映射到 HTTP请求上,而 RPC 应答映射到 HTTP 应答。然而,在 RPC上使用 SOAP 并不仅限于 HTTP 协议绑定。SOAP协议没有和 HTTP 有很好的结合,没有利用 HTTP协议里面关于 request 和 response的丰富词汇,而是鼓励应用设计人员定义任意的词汇(动词和名词),像getUsers(),savePurchaseOrder(...),getBookPrice() 等。SOAP RPCRequest 通过 HTTP POST 请求发送。清单 1 和清单 2给出了 SOAP RPC over HTTP 的 request 和 response的示例。请求和响应是封装在 SOAP Envelope 里面,以HTTP request 和 response 的 body 传送。
清单 1. A SOAP Request over HTTP示例
POST /books/bookquery HTTP/1.1 Host: www.Monson-Haefel.com Content-Type: text/xml; charset="utf-8" Content-Length: nnn SOAPAction="" <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.example.com/books"> <soap:Body> <mh:getBookPrice> <id>123456</id> </mh:getBookPrice> </soap:Body> </soap:Envelope> |
从清单 1 可以看出,soap envelope是定义好的格式,它描述所要调用的方法和方法所需要的参数及其参数值,在HTTP 上,它作为 HTTP request 的 body 发送。
清单 2. A SOAP response over HTTP 示例
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:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh=" http://www.example.com/books" > <soap:Body> <mh:getBookPriceResponse> <result>24.99</result> </mh:getBookPriceResponse> </soap:Body> </soap:Envelope> |
从清单 2 可以看出,soap envelope 封装了 getBookPrice的调用结果,在 HTTP 上,它作为 HTTP response 的 body返回回来。
XML RPC over HTTP 和 SOAP RPC over HTTP从结构上看很类似。这种远程过程调用使用 HTTP作为传输协议,XML作为传送信息的编码格式。XML-RPC的定义尽可能的保持了简单,但同时能够传送、处理、返回复杂的数据结构。XML-RPC是工作在 Internet 上的远程过程调用协议。一个XML-RPC 消息就是一个请求体为 XML 的 HTTP POST请求,被调用的方法在服务器端执行并将执行结果以XML 格式编码后返回。清单 3 和清单 4 给出了 XML RPCover HTTP 的 request 和 response的示例。请求和响应是封装在一个固定的格式里面,以HTTP request 和 response 的 body 传送。
清单 3. A XML RPC Request over HTTP 示例
POST /books/bookquery HTTP/1.1 Host: www.Monson-Haefel.com Content-Type: text/xml; charset="utf-8" Content-Length: nnn Connection:keep-alive <?xml version="1.0"?> <methodCall> <methodName>getBookPrice</methodName> <params> <param> <value><string>123456</string></value> </param> </params> </methodCall> |
从清单 3 可以看出,methodcall 是定义好的 XML格式,它描述所要调用的方法和方法所需要的参数及其参数值,在HTTP 上,它作为 HTTP request 的 body 发送。
清单 4. A XML RPC response over HTTP 示例
HTTP/1.1 200 OK Content-Type: text/xml; charset='utf-8' Content-Length: nnn Connection: close <?xml version="1.0"?> <methodResponse> <methodName>getBookPrice</methodName> <params> <param> <value><double>24.99</double></value> </param> </params> </methodResponse> |
从清单 4 可以看出,methodResponse 封装了 getBookPrice的调用结果,在 HTTP 上,它作为 HTTP response 的 body返回回来。
REST 风格的架构也并不强调和协议的绑定。HTTP 是WWW网上广泛使用的并且被证明是有效的通信协议,所以现在RESTful 服务基本也是基于 HTTP 协议的。资源是由 URI来指定。
对资源的操作包括获取、创建、修改和删除资源,这些操作正好对应HTTP 协议提供的 GET、POST、PUT 和 DELETE方法。通过操作资源的 representation来操作资源。资源的 representation 可以是 XML也可以是 HTML,取决于读者是机器还是人,是消费web 服务的客户软件还是 web浏览器。当然也可以是任何其他的格式。清单 5和清单 6 给出了 REST over HTTP 的 request 和 response的示例。
清单 5. A REST Request over HTTP 示例
GET /books/123456/xml HTTP/1.1 Host: example.com |
清单 6. A REST Response over HTTP 示例
HTTP/1.1 200 OK Date: Fri, 10 Sept 2010 17:15:33 GMT Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT ETag: "2b3f6-a4-5b572640" Accept-Ranges: updated Content-Type: text/xml; charset="utf-8" <book category="CHILDREN"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>24.99</price> </book> |
从清单 5 和清单 6 种,可以清楚的看到,REST在最大程度上充分利用了HTTP,没有增加额外的词汇和约定。
通过前面的分析和比较,我们可以清楚地看到,REST风格的 web 服务与 SOAP RPC 和 XML RPC 风格的 web服务相比,http request 更简单,response的语意更清楚。而且客户端不需要知道那么多关于应用的细节,比如method name,method 调用的参数等等。
简言之,目前在三种主流的 Web服务实现方案中,因为 REST 模式的 Web服务与复杂的 SOAP 和 XML-RPC对比来讲明显的更加简洁,越来越多的 web服务开始采用 REST 风格设计和实现。例如,Amazon.com提供接近 REST 风格的 Web服务进行图书查找;雅虎提供的 Web 服务也是 REST风格的。
REST 风格的 web服务已被广泛的接受和使用,但是在使用的过程中,我们发现其实有很多号称RESTful 的 web 服务并不是 Roy 定义的 REST服务,或者违背了其中的一些约束。像 Amazon 和Flickr 的 web服务。接下来,我们首先结合实际经验,重新解读REST 架构风格中的核心概念,帮助读者准确的掌握REST 架构,最后给出一个完全符合 REST 风格架构的web 服务定义的例子。
Representational StateTransfer
在理解 REST相关的核心概念之前,我们来看看“REST”本身应该怎么理解。“RepresentationalStateTransfer”是一个不完整的句子,“Representational”代表的是什么的“表示”?“State”又指的是什么的状态?“Transfer”的又是什么?如果我们要把它补全该如何呢?根据Roy 的论文和我们的实践,应该是“Application StatesTransfer among the resource ’ srepresentation”。这里的“State”指的是“应用”的“状态”,这个“状态”是用“资源的表示”来代表的,用户可以在“状态”之间“跳转”。
REST 是一种架构的风格,它是对分布式 hypermidea系统的架构元素的抽象,提供了一些核心的概念和指导思想。这种架构风格是“客户端-服务器”架构风格的一种,也就是说“客户端”发起“请求”,“服务器”处理“请求”并返回相应的结果。REST的贡献是规定了一系列的方法论,在“请求”方面,规定“客户端”怎么发起“请求”,发的是什么样的“请求”,以什么协议发“请求”;在处理方面,规定“服务器”怎么响应“请求”,返回什么样的“响应”,系统后续应该怎么“跳转”等等。让我们再回顾Roy 定义的这些约束。
“无状态”(Stateless)
“客户端”和“服务器端”的交互是“无状态”的,也就是说“请求”之间是相互隔离的。“服务器端”不保存“客户端”的应用上下文(context),每个从“客户端”来的“请求”都必须包括所有的必要的信息让“服务器端”能够完全理解“请求”并处理。“客户端”存了很多“会话”的信息。让我们以搜索引擎为例来看看“有状态”和“无状态”的区别。图1 和图 2分别给出了两个搜索引擎的“有状态”和“无状态”的交互示例。
图 1. 无状态的搜索引擎的交互示例
![REST Service 的最佳实践,第 1 部分: 重新解析 REST Service - 蓝色珊瑚礁 - 蓝色珊瑚礁 图 1. 无状态的搜索引擎的交互示例](https://i-blog.csdnimg.cn/blog_migrate/b7927790fcf056f1055efc92db4932d8.png)
图 1所示是无状态的搜索引擎的请求(request)和响应(response)的实例。可以清楚地看出,每个request 和 response都是互相独立的,相互之间没有数据的依赖。每个request 包含服务器端响应这个 request所需要的所有信息。 以“搜索SOAP”为例,用户首先发了 requesthttp://www.google.com/search?q=SOAP,并且得到了搜索结果,其中包含了10个最相关的搜索结果。这个交互过程就结束了,服务器端没有保存任何和这个请求相关的信息。但是在这个返回的状态中,服务器端把下一步的可能的状态嵌在其中了。比如用户如果在这些结果没有找到自己想要的结果,他可以翻页,翻到第二页。第二页是另一个状态,这时用户点击了第二页,然后客户端又发了一个requesthttp://www.google.com/search?q=SOAP&start=10,这个request 了包含了所有的上下文,也就是“按关键字SOAP 搜索并且以第 10个搜索结果开始返回”。也就是说,服务器把当前的状态隐含中结果中返回,客户端保存下这些隐含的状态,而不是保存在服务器端。客户端可以根据服务器端返回来的状态构建下一个状态的请求。
“无状态”的好处是每个状态都有一个对应的URI,这些 URI 可以bookmark,可以使得用户方便的在浏览器中前进后退,可以在用户希望的任何时候访问任意多次。
图 2. 有状态的搜索引擎的交互示例
![REST Service 的最佳实践,第 1 部分: 重新解析 REST Service - 蓝色珊瑚礁 - 蓝色珊瑚礁 图 2. 有状态的搜索引擎的交互示例](https://i-blog.csdnimg.cn/blog_migrate/474106358b540e32c350701ac498e925.png)
图 2 是有状态的搜索引擎的 request 和 response的交互示例。可以看出,request之间的时序依赖性。以“搜索SOAP”为例,用户在看 1~10 个搜索结果并想看 11~20个结果,客户端只需要发一个“start=10”的请求而是发“/search?q=SOAP&start=10”的请求,也就是客户端不用重复前面的request 的上下文。这使得 HTTP request相对简单了很多。但是他使得 HTTP protocol变得复杂。服务器端和客户端需要同步会话的状态,在可靠网络上,这是一个复杂的任务。
“无状态”带来了一些性能的提升。在“visibility”方面,对于监控系统而言,它不用去关心跨请求的数据对当前请求的影响,所以“visibility”有所提升。在“reliability”方面,由于“客户端”保存了很多“会话”数据,因此减少了部分“服务器”端的故障或者网络故障对应用的影响,因此对于用户来说,“reliability”有所提升。最值得一提的是“scalability”,由于“服务器”端能够独立的响应每个request,而不用依赖会话历史,因此少了很多“资源”的管理和同步,使得多个服务器可以同时服务不同的用户的请求,获得很好的自由扩展功能。
当然,事务都有两面性,“无状态”带来的不足之处包括可能的网络性能的降低,因为“服务器”会发很多重复的数据到不同的“客户端”,使得“客户端”保存所有的会话信息;这也导致了另一个问题,就是“服务器”将失去对应用的一致行为的一部分控制权,而且还对“客户端”的实现产生依赖。
“缓存”(Cacheable)
为了提高 REST 风格架构的网络性能,Roy加入了“缓存”的约束。“缓存”不是一个新的概念,HTTP协议提供机制,使得“客户端”可以“缓存”一些数据。在“服务器”返回的“响应”中,可以隐式或者显式的指明数据的“缓存”属性,这样,“客户端”可以在未来使用“缓存”下来的数据,减少“客户端”和“服务器”端的交互次数,从而达到提高网络性能的目的。
统一的接口 (Uniform Interface)
这是 REST风格的架构区别于其他架构的一个最关键的指标,就是REST风格的架构对于任意应用,规定了统一的接口定义。接口定义包括四个部分:
- 1)identification of resources(标识资源);
- 2)manipulation of resources throughrepresentations(通过资源的表示来操作资源);
- 3)self-descriptive messages(自描述的信息);
- 4)hypermedia as the engine of applicationstate(超媒体作为应用状态的引擎)
下面分别对这些概念进行解析。
Identification of resources
第一部分“identification of resources”讲了 REST架构风格的一个最核心的概念“Resource”以及“Resource”的“identification”。“Resource”是信息系统的一种抽象,可以是任何重要的足以把本身作为一个引用(reference)的事物。从应用的角度看,如果应用的用户需要“创建一个到它链接”,获取或者缓存它的“表示”,或者想对他做些操作,那么都可以创建一个“Resource”。一个“Resource”可以是现实世界的事物,比如一本书,一辆车,一栋房子;也可以是一个虚拟的概念,也可以是一个算法的结果。“identification”是“Resource”的全局唯一的标识,如果“Resource”没有identification,那么他不能称为“Resource”。用 URI来表示一个 Resource 的 identification。关于 URI的最佳实践包括 URI是自描述的,有结构的,这样可以使得“客户端”可以自己创建一些有预测性的请求。几个比较好的Resource URI 示例:
http://www.example.com/books/123456 http://www.example.com/softwares/im/db2/9.5 http://www.example.com/blog/2010/09/10/1 http://www.example.com/bugs/by-state/new |
前面解释了什么是 Resource,怎么用 URI 来标识一个Resource,下面来看下 Resource 和 URI的关系,他们之间是不是一一对应的呢?让我们来回答下面几个问题:1)两个URI 能指向同一个 Resource么?回答是肯定的。作为程序员,我们都有这样的经历,就是经常做一些release,让我们考虑这两个 URI:http://www.example.com/releases/3.1.1 和http://www.example.com/releases/latest,在特定的时刻,他们指向的就是同一个resource,但是背后的概念是不一样的,一个是指向一个特定的release 版本号,一个是指向一个随着时间演进的resource