关于REST及RESTful的概念,已有不少文章介绍,这里整理几篇我觉得不错的参考:
- 维基百科的定义: REST
- 什么是REST跟RESTful? REST理论的中文详述,其中你可以了解到WCF Restful属于RPC 样式的 Web 服务,ASP.NET Web API属于RESTful Web 服务。
- 深入浅出REST InfoQ的专文介绍,文中甚至有Roy T. Fielding当年REST博士论文的中文翻译链接。另外值得一提的,大家可能没听过Roy Fielding的大名,但如果得知他是HTTP规格的主要作者及Apache HTTP Server项目的发起人之一,应该不会有人怀疑他在Web技术领域的分量。
上面的文章建议大家认真的读一下,这里我们简要的介绍下REST 做入门介绍,理解整个 REST 能让我们在 ASP.NET Web API 的路上更顺畅。
REST是什么?
REST ( REpresentational State Transfer ),State Transfer 为 "状态传输" 或 "状态转移 ",Representational 中文有人翻译为"表征"、"具象",合起来就是 "表征状态传输" 或 "具象状态传输" 或 "表述性状态转移",不过,一般文章或技术文件都比较不会使用翻译后的中文来撰写,而是直接引用 REST 或 RESTful 来代表,因为 REST 一整个观念,想要只用六个中文字来完整表达真有难度。
REST 一词的出于《Architectural Styles and
the Design of Network-based Software Architectures》论文,我们先简单从标题来看,它应该是一种架构样式 (Architectural Styles) 与软件架构 (Software Architectures),而且是以网络 (Network-based) 为基础,重点就是:
- 架构样式 (Architectural Styles)
- 软件架构 (Software Architectures)
- 网络 (Network-based) 为基础
REST 本身是设计风格而不是标准。REST 谈论一件非常重要的事,如何正确地使用 Web标准,例如,HTTP 和 URI。想要了解 REST 最好的方式就是思索与了解Web 及其工作方式。如果你设计的应用程序能符合 REST 原则 (REST principles),这些符合 REST 原则的 REST 服务可称为 "RESTful web service" 也称 "RESTful Web API"。"-ful" 字尾强调它们的设计完全符合 REST 论文里的建议内容。
资源 RESOURCE
在 REST 中的资源 (Resource) 代表整个网络上的资源。网络上提供了各式各样的资源,而网络上的资源由 URI (统一资源标识符,Uniform Resource Identifier) 来提供。
回想,你如何连上我的 博客,你可能通过浏览器直接输入 www.cnblogs.com/shanyou 此域名来到达首页,也能用书签或网络上的链接,经点击后来连上我的博客。然后,你想看这一篇名为「REST 入门介绍」的文章,所以以你接下去点击这文章的标题连结,接去下阅读。我们简易了解一下整个流程:
- 通过URL ( http://www.cnblogs.com/shanyou ) , Client 向 http://www.cnblogs.com/shanyou 发出请求
- www.cnblogs.com/shanyou 收到请求,回应首页给 Client
- Client 又点击 REST 文章连结 (假设是 http://www.cnblogs.com/shanyou/archive/2011/06/30/2095018.html) 向http://www.cnblogs.com/shanyou发出archive/2011/06/30/2095018.html 此篇文章的请求
- www.cnblogs.com/shanyou 收到请求,响应 REST 文章内容给 Client
Client 的通过 URI 来获取资源的具体象征 (Representational)。Client 取得这些具体象征使这些应用程序转变其状态 (以 浏览器而言,取得 HTML、CSS、JavaScript … 来生成界面),随着不断取得资源的具体象征, Client 端不断地改变其状态,这样不断的反复 (iterations ) 过程就是所谓的 Representational State Transfer。
使用 WEB 标准
上述是最接近日常的范例,这些行为在 HTTP 规范中称之为 GET,也就是通过URL 来 GET 我想要的资源。另一常用的例子是填写表单,例如,登入表单,我想进行登入动作,就必须先发送账号与密码给某一资源,此资源会验证你所传送的数据是否正确,再进行后续动作。我们发送信息给资源的行为在 HTTP 规范中称之为 POST。
在 HTTP/1.1 RFC 2616第 5.1.1 Method 一节定义了八大类 HTTP 方法,除了我们常用的 GET 与 POST 之外,在 REST 中常用的还有 PUT 与 DELETE。此 GET, POST, PUT, DELETE 正好可以对应我们 CRUD (Create, Read, Update, Delete) 四种数据操作。
HTTP Method 与 CURD 数据处理操作对应 | ||
HTTP方法 | 数据处理 | 说明 |
POST | Create | 新增一个没有id的资源 |
GET | Read | 取得一个资源 |
PUT | Update | 更新一个资源。或新增一个含 id 资源(如果 id 不存在) |
DELETE | Delete | 删除一个资源 |
RESTFUL WEB SERVICE
RESTful Web Service (又称 RESTful Web API) 是一个使用 HTTP 并符合 REST 原则的 Web 服务。我们知道,通过 URL 可以传送 GET 请求,在 表单指定 method="GET|POST" 来送出请求。但我们要处理 PUT 或 DELETE 的请求呢?通过 RESTful 我们可以简单 URI 来定义资源并和 HTTP 方法配合使用。
Resource 与 HTTP 方法的对应 | |||||
资源 | 资源说明 | GET | PUT | POST | DELETE |
http://www.cnblogs.com/Products/ | Products是一组资源集合 | 列出 该组资源集合中每个资源的详细信息 | 更新 当前整组资源 | 新增 或附加一个新资源。该操作传回新资源的URL | 删除 整组资源 |
http://www.cnblogs.com/Products/1 | Products/1是单个资源 | 取得 指定的资源的详细信息 | 更新 或新增指定的资源 | 新增 或附加一个新元素 | 删除 指定的元素 |
以上表格有没有很像我们一般在对数据库表格的操作顺序,进入一个 Table 的数据首页 (通常是列表),此页面会有「新增、更新、删除、详细」等连结,你想进行什么操作,就点那一个连结。
在 RESTful 每个资源有自己独立的 URI, Client 从资源集合或单个资源开始进入,不管是资源集合或单个资源,我们都能与 HTTP 方法配合使用,例如,GET 下载,PUT 更新,POST 新增,DELETE 删除。
ASP.NET Web API 是一个框架(framework),能让你在 .NET Framwork 之上架设 HTTP 服务 (HTTP Services)。ASP.NET Web API 是 .NET Framework 上构建 RESTful 应用程序的理想平台。
在 Julie Lerman's 的 How I see Web API 一文中,用了一张图来简明说明 Web API:
Web服务是一种面向服务的架构的技术,通过标准的 Web 协议提供服务,目的是保证不同平台的应用服务可以互操作。根据W3C的定义,Web服务(Web service)应当是一个软件系统,用以支持网络间不同机器的互动操作。网络服务通常是许多应用程序接口(API)所组成的,它们通过网络,例如国际互联网(Internet)的远程服务器端,执行客户所提交服务的请求。流行的或者曾经流行的Web服务架构有三种:SOAP RPC over HTTP, XML RPC over HTTP和REST over HTTP。下面分别简要介绍这三种架构。
SOAP RPC over HTTP
简单对象访问协议(SOAP,全写为 Simple Object Access Protocol)是一种标准化的通讯规范,主要用于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 RPC Request通过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 RPC over 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 over HTTP
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服务定义的例子。
REST核心概念解读
Representational State Transfer
在理解REST相关的核心概念之前,我们来看看“REST”本身应该怎么理解。“Representational State Transfer”是一个不完整的句子,“Representational”代表的是什么的“表示”?“State”又指的是什么的状态 ?“Transfer”的又是什么?如果我们要把它补全该如何呢?根据Roy的论文和我们的实践,应该是“Application States Transfer among the resource ’ s representation”。这里的“State”指的是“应用”的“状态”,这个“状态”是用“资源的表示”来代表的,用户可以在“状态”之间“跳转”。
REST架构风格定义的约束
REST是一种架构的风格,它是对分布式hypermidea系统的架构元素的抽象,提供了一些核心的概念和指导思想。这种架构风格是“客户端 - 服务器”架构风格的一种,也就是说“客户端”发起“请求”,“服务器”处理“请求”并返回相应的结果。REST的贡献是规定了一系列的方法论,在“请求”方面,规定“客户端”怎么发起“请求”,发的是什么样的“请求”,以什么协议发“请求”;在处理方面,规定“服务器”怎么响应“请求”,返回什么样的“响应”,系统后续应该怎么“跳转”等等。让我们再回顾Roy定义的这些约束。
“无状态”(Stateless)
“客户端”和“服务器端”的交互是“无状态”的,也就是说“请求”之间是相互隔离的。“服务器端”不保存“客户端”的应用上下文(context),每个从“客户端”来的“请求”都必须包括所有的必要的信息让“服务器端”能够完全理解“请求”并处理。“客户端”存了很多“会话”的信息。让我们以搜索引擎为例来看看“有状态”和“无状态”的区别。图 1 和图 2 分别给出了两个搜索引擎的“有状态”和“无状态”的交互示例。
图 1. 无状态的搜索引擎的交互示例
图 1 所示是无状态的搜索引擎的请求(request)和响应(response)的实例。可以清楚地看出,每个request和response都是互相独立的,相互之间没有数据的依赖。每个request包含服务器端响应这个 request 所需要的所有信息。 以“搜索SOAP”为例,用户首先发了request http://www.google.com/search?q=SOAP,并且得到了搜索结果,其中包含了10个最相关的搜索结果。这个交互过程就结束了,服务器端没有保存任何和这个请求相关的信息。但是在这个返回的状态中,服务器端把下一步的可能的状态嵌在其中了。比如用户如果在这些结果没有找到自己想要的结果,他可以翻页,翻到第二页。第二页是另一个状态,这时用户点击了第二页,然后客户端又发了一个request http://www.google.com/search?q=SOAP&start=10,这个request了包含了所有的上下文,也就是“按关键字SOAP搜索并且以第10个搜索结果开始返回”。也就是说,服务器把当前的状态隐含中结果中返回,客户端保存下这些隐含的状态,而不是保存在服务器端。客户端可以根据服务器端返回来的状态构建下一个状态的请求。
“无状态”的好处是每个状态都有一个对应的URI,这些URI可以bookmark,可以使得用户方便的在浏览器中前进后退,可以在用户希望的任何时候访问任意多次。
图 2. 有状态的搜索引擎的交互示例
图 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 through representations(通过资源的表示来操作资源);
3)self-descriptive messages(自描述的信息);
4)hypermedia as the engine of application state(超媒体作为应用状态的引擎)
下面分别对这些概念进行解析。
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,是两个完全不同的概念。所以一个resource可以有一个或者多个URI来标识。2)一个URI能指向不同的resource么?答案是否定的。这和 Universal Resource Identifier的原则相违背。
Manipulation of resources through representations
接着来看看“Representation”。Representation是Resource的表现形式。Resource是信息系统的抽象,但是最终必须以人们能理解的一种形式提供出来。可以是一个XML文档,HTML文档,CSV,file等等。一个Resource可以有多种Representation。那么服务器端怎么知道用户想要哪个 Representation 呢?通常来讲有两种方式:
1)在resource 的URI里面指明,为不同的Representation 提供不同的 URI。举个例子,我们有个book的resource,我们提供几个不同的 URI 来表示不同的representation。清单7、8、9分别是XML、JSON、CSV三种representation的HTTP request和response的示例。
清单 7. Representation为XML的request和response示例
以下是引用片段: GET /books/123456/xml HTTP/1.1 Host: example.com Reponse: 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> |
清单 8. Representation为JSON的request和response示例
以下是引用片段: GET /books/123456/json HTTP/1.1 Host: example.com Reponse: 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/json; charset="utf-8" [ "book": { "title":Harry Potter, "author":J K. Rowling, "year":2005, "price":24.99 } ] |
清单 8. Representation为CSV的request和response示例
以下是引用片段: GET /books/123456/csv HTTP/1.1 Host: example.com Reponse: 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/csv; charset="utf-8" title,author,year,price Harry Potter,J K. Rowling,2005,24.99 |
2) 利用HTTP的内容协商(content negotiation)机制。客户端可以利用“Accept”header 指明它希望的内容表示形式。还是拿book的例子来说,客户端发的HTTP请求如清单 9 所示:
清单 9. 利用content negotiation机制请求representation的request示例
以下是引用片段: GET /books/123456 HTTP/1.1 Host: example.com Accept: text/xml GET /books/123456 HTTP/1.1 Host: example.com Accept: text/json GET /books/123456 HTTP/1.1 Host: example.com Accept: text/csv |
对应的response和第一种方式相同,所以忽略了。
相比较这两种方式,根据我们的经验,第一种方式更好,URI是一种好的实践,用来表示resource的各方面的信息,包括结果,版本,representation形式,时间等等,它带来的好处是使客户端的工作大大降低,它很方便的可以在不同的应用之间共享。
那么怎么对 resource 进行下一步的操作(“manipulation”)呢? REST对Resource的操作定义和HTTP method是对应起来的。HTTP协议定义 GET、POST、PUT、DELETE 来表示对资源的获取、创建、更新和删除。“Manipulation of resources through representations”约束规定了 resource 的操作是通过 representation 来完成的,所以在resource的representation里面,需要包含对resource的操作怎么进行。APP(ATOM Publishing Protocol)提供了一个最佳实践,是用 <link> 的方式指明Resource响应的操作,下面是一个实例:
以下是引用片段: <link rel="edit" href="http://www.example.com/books/123456"/> |
rel定义了该link的resource和当前resource的关系,href定义了resource的HTTP endpoint。所以当用户想更新id为123456的book的价钱的时候,客户端会发一个PUT请求到指定的URI,示例如清单 10 所示:
清单 10. 更新resource的request示例
以下是引用片段: PUT /books/123456 HTTP/1.1 Host: example.com Reponse: HTTP/1.1 200 OK Date: Fri, 10 Sept 2010 17:15:33 GMT Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT <book category="CHILDREN"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>31.99</price> </book> |
同样,客户端可以发HTTP请求去删除这个book,如清单 11 所示。
清单 11. 删除resource示例
以下是引用片段: DELETE /books/123456 HTTP/1.1 Host: example.com Reponse: HTTP/1.1 200 OK |
Self-descriptive messages
REST接口的定义强调了自描述“Self-descriptive”性。自描述性也是为了让客户端充分的理解当前的状态,下一步的状态怎么跳转。
前面讲了resource的representation,只讲述了representation的数据的一部分。在representation里面,除了包含数据部分,还包括元数据,它是用来描述数据的信息,或者是一些客户端需要知道的一些领域知识。这里以ATOM协议为例看看怎么在representation里面提供自描述的信息。清单 12 是一个ATOM的片段。
清单 12. 一个ATOM形式的resource representation
以下是引用片段: <?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:os="http://a9.com/-/spec/opensearch/1.1/" xmlns:catalog="http://www.ibm.com/opensearch/1.0"> <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 </id> <link href="http://www.example.com/books?q=harry potter&start=0" rel="self"></link> <link href="http://www.example.com/books?q=harry potter&start=10" rel="next"></link> <updated>2009-08-11T03:00:27.062Z</updated> <title type="text"> harry potter 相关的图书 </title> <os:startIndex xmlns:os="http://a9.com/-/spec/opensearch/1.1/">0</os:startIndex> <os:itemsPerPage xmlns:os="http://a9.com/-/spec/opensearch/1.1/">10</os:itemsPerPage> <os:totalResults xmlns:os="http://a9.com/-/spec/opensearch/1.1/">10</os:totalResults> <entry> <title type="text"> Harry Potter </title> <id>urn:id:23-27</id> <updated>2009-07-09T11:01:26.000Z</updated> <category term="harry potter"></category> <category term="best seller"></category> <category term="book"></category> <category term="Hyper Service"></category> <content type="application/xml" xmlns= ’ http://www.example.com/books ’ > <book category="CHILDREN"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>31.99</price> </book> </content> </entry> … </feed> |
从上面的例子可以看出,除了<content>里面的内容是和book数据相关的信息以外,剩下的信息都是描述性的信息,通常包括翻页信息:一共有多少页,前后翻页的link是什么;分类信息,时间信息,还有一些文本信息供用户阅读。
总之,服务器端尽可能的返回详细的信息,帮助客户端理解当前的状态,以及发起下一个请求所需要的所有信息,以达到“服务器端无状态”和客户端发起“状态跳转”的目的。
Hypermedia as the engine of application state
“Hypermedia as the engine of application state”是“统一接口”的最后一个约束,也是最重要的一个约束,不幸的是, Roy的REST论文中对这个约束的解释特别少,在这里,我们根据我们的经验和理解,对这个约束进行描述。这个约束其实规定的是应用系统是通过Hypermedia的方式在不同的状态之间跳转。这句话听起来有点拗口,还是来看一个例子吧。
Flickr提供了REST API,以flickr.groups.members.getList为例来看看这个REST API的 定义,清单 13 是一个响应示例(sample response),可以看出“members”是一个resource,它是一系列“member”的集合,也就是说,它指向其他的resource。
清单 13. flickr.groups.members.getList的响应示例
以下是引用片段: <members page="1" pages="1" perpage="100" total="33"> <member nsid="123456@N01" username="foo" iconserver="1" iconfarm="1" membertype="2"/> <member nsid="118210@N07" username="kewlchops666" iconserver="0" iconfarm="0" membertype="4"/> <member nsid="119377@N07" username="Alpha Shanan" iconserver="0" iconfarm="0" membertype="2"/> <member nsid="67783977@N00" username="fakedunstanp1" iconserver="1003" iconfarm="2" membertype="3"/> ... </members> |
如果用户还想对“Alpha Shanan”了解更多呢?这时,客户端会构建一个HTTP request,URI为: http://api.flickr.com/services/rest/?method=flickr.people.getInfo?auth_key=xxxx&user_id=119377@N07
如果系统按照这种方式运行,问题在哪呢?客户端和服务器端需要有很多的共享的知识和约定,客户端需要知道获取人员信息的API是 method=flickr.people.getInfo以user_id作为参数。这不是Hypermedia,同时也违背了Roy的关于“Hypermedia as the engine of application state”的约束,所以这个不是好的RESTful的实现。
Hypermedia的实质是hyperlink, 用hyperlink把这些相互依赖的resource联系起来,这些hyperlink是由服务器端生成并且在representation里面返回来的,包括了当前的状态集合和可能的下一步的状态集合。客户端不需要任何domain specific知识就能够实现状态的跳转。Hypermedia类型的sample response如清单 14 所示。在清单 14 中可以看到,resource和resource之间的联系通过hyperlink关联起来,并且在resource的representation里面表示。
清单 14. REST风格的flickr.groups.members.getList的sample response
以下是引用片段: <members page="1" pages="1" perpage="100" total="33"> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=123456@N01" username="foo" iconserver="1" iconfarm="1" membertype="2"/> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=118210@N07" username="kewlchops666" iconserver="0" iconfarm="0" membertype="4"/> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=119377@N07" username="Alpha Shanan" iconserver="0" iconfarm="0" membertype="2"/> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=67783977@N00" username="fakedunstanp1" iconserver="1003" iconfarm="2" membertype="3"/> ... </members> |
通过前面关于“统一接口”的解析,我们清楚地知道,REST风格的架构使得web服务的“客户端”和“服务器端”很好的分离开来。“客户端”不需要关心数据的存储,使得“客户端”的可移植性(portability)提高了。“服务器端”不用关心用户的接口和用户的状态,所以“服务器端”将变得更加简单,而且方便的获得更好的可伸缩性(scalability)。只要保持接口的定义不变 ,“客户端”和“服务器端”可以独立的开发和演变。
一个定义良好的RESTful Web服务接口
首先,用一个文档描述web服务的capabilities以及服务的location。如清单 15 所示。
清单 15. 用APP协议描述的服务capabilities和location
以下是引用片段: <?xml version="1.0" encoding="UTF-8"?> <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom"> <workspace> <atom:title type="text"> InfoSphere MashupHub </atom:title> <collection href="http://localhost:8080/mashuphub/atom?collection=all"> <atom:title type="text"> All </atom:title> <accept> application/atom+xml; type=entry </accept> <categories> <categories> ALL </categories> </categories> </collection> <collection href="http://localhost:8080/mashuphub/atom?collection=feeds"> <atom:title type="text">Feeds</atom:title> <accept> application/atom+xml;type=entry </accept> <categories> <categories> feeds </categories> </categories> </collection> … </workspace> </service> |
这个服务文档指明InfoSphere MashupHub提供了一系列的web服务,包括list所有的资源,list所有的feeds等等。并且还描述了每个web服务的 location可以提供的resource representation的类型。这种描述文档对于web服务的客户端来说非常有益。这个服务描述文档本身也是一个 resource,可以用HTTP URI定位到。
其次,我们来看一下每个web服务是怎么定义的。
清单 16. RESTful web服务接口的定义示例
1) Resource:所有的 feeds
2) Resource URI:http://localhost:8080/mashuphub/atom?collection=feeds
3) Resource representation:
以下是引用片段: <feed xmlns="http://www.w3.org/2005/Atom"> <id> http://9.186.64.113:8080/mashuphub/atom?collection=feed </id> <link href="http://9.186.64.113:8080/mashuphub/atom?collection=feeds" rel ="self"></link> <updated> 2010-09-11 T 15:12:24.968Z </updated> <title type = "text"> feeds Collection </title> <author> <name> InfoSphere MashupHub Search Feed</name> </author> <entry> <author> <name> InfoSphere MashupHub Search Feed </name> </author> <title type="html"> MyCo Customer List </title> <updated> 2010-09-07 T 05:08:52.000Z </updated> <id> urn:id:1460 </id> <link href="http://9.186.64.113:8080/mashuphub/atom?collection=feeds&id=1460" rel="self"></link> <link href="http://9.186.64.113:8080/mashuphub/atom?collection=feeds&id=1498" rel="related" title = "MyCo Customer List with weather info" > </link> <category term="feed"> </category> <content type = "application/xml" > <catalog:feed xmlns:catalog="http://www.ibm.com/xmlns/atom/opensearch/feed/1.0/"> <catalog:version> 1.0 </catalog:version> <catalog:name> MyCo Customer List </catalog:name> <catalog:author> admin </catalog:author> <catalog:permission> public </catalog:permission> <catalog:description> </catalog:description> <catalog:rating> 0.0 </catalog:rating> <catalog:useCount> 320 </catalog:useCount> <catalog:dateModified> 1283836132 </catalog:dateModified> <catalog:numComments> 0 </catalog:numComments> <catalog:tags> </catalog:tags> <catalog:categories> </catalog:categories> <catalog:downloadURL>http://9.186.64.113:8080/mashuphub/client /plugin/generate/entryid/1460/pluginid/3 </catalog:downloadURL> <catalog:mimetype> application/atom+xml </catalog:mimetype> </catalog:feed> </content> </entry> </feed> |
4) Http method: GET
5) 和别的resource的关系。
在representation中用 <link>
定义了“MyCo Customer List”和“MyCo Customer List with weather info”的关系
总结一下,一个设计良好的RESTful web服务应该包括一下几个定义:
1) 服务描述文档,如清单 15 所示;
2) 资源 (resource) 及其identification;
3) 资源的representation的定义
4) 用HTTP method表示的资源的manipulation
5) 在资源的representation里面以Hyperlink表述的资源和资源之间的关系
结束语
REST是一种架构风格,汲取了WWW的成功经验:无状态,以资源为中心,充分利用HTTP协议和URI协议,提供统一的接口定义,使得它作为一种设计Web服务的方法而变得流行。在某种意义上,通过强调URI和HTTP等早期Internet标准,REST是对大型应用程序服务器时代之前的Web方式的回归。
本文根据Roy的论文,结合实际的例子,重新解析了REST中的核心概念。根据作者多年的创建和使用RESTful web服务的经验,更多的是在观念上的澄清。最后通过一个RESTful web服务,告诉读者一个真正意义上的RESTful web服务是什么样子。但愿本文的讨论会您提供了一定意义上的思想上的指导和实践上的指导。