REST模式

  REST(REpresentational State Transfort) 形式上应该表述为客户端通过申请资源来实现状态的转换,在这个角度系统可以看成一台虚拟的状态机。抛开R. T. Fielding博士论文里晦涩的理论不说,REST应该满足这样的特点:

  1. 客户端和服务器结构
  2. 连接协议具有无状态性
  3. 能够利用Cache机制增进性能
  4. 层次化的系统
  5. 按需代码

  说到底,REST只是一种架构风格,而不是协议或标准。但这种新的风格(也许已经历史悠久?)对现有的以SOAP为代表的Web Service造成的冲击也是革命性的,因为它面向资源,甚至连服务也抽象成资源,因为它和HTTP紧密结合,因为它服务器无状态。

  REST与SOAP的区别:因为SOAP并不假定传输数据的下层协议,因此必须设计为能在各种协议上运行。即使绝大多数SOAP是运行在HTTP上,使用URI标识服务,SOAP也仅仅使用POST方法发送请求,用一个唯一的URI标识服务的入口。举一个图书馆在线查询管理系统为例,服务提供者必须为每一本书提供一个内部标识,然后可能定义一个listBooks操作来返回一系列图书,一个getBook操作来返回指定的图书,一个createBook操作来向数据库加入新增的图书,一个deleteBook操作来删除作废的图书,每个操作都有各自的参数,尤其是用内部标识来标识操作的图书。这种设计被诟病之处,在于deleteBook操作也要用POST方法来发送,而其实HTTP协议有更和逻辑的DELETE方法可用。REST正是这样设计的,REST为每一个资源(此处是图书)指定一个唯一的URI,而用HTTP的4种方法GET、POST、PUT、DELETE直观地表示获取、创建、更新和删除图书。同时图书集合也是和单本的图书不同的资源,如果用/books来代表图书列表,/books/ID来代表标识为ID的图书,那么对/books的GET操作就代表返回整个图书列表,对/books/ID的DELETE操作代表删除指定的图书,等等。

  REST的优点:REST简单而直观,把HTTP协议利用到了极限,在这种思想指导下,它甚至用HTTP请求的头信息来指明资源的表示形式(如果一个资源有多种形式的话,例如人类友善的页面还是机器可读的数据?),用HTTP的错误机制来返回访问资源的错误。由此带来的直接好处是构建的成本减少了,例如用URI定位每一个资源可以利用通用成熟的技术,而不用再在服务器端开发一套资源访问机制。又如只需简单配置服务器就能规定资源的访问权限,例如通过禁止非GET访问把资源设成只读。服务器无状态带来了更多额外好处,因为每次请求都包含响应需要的所有信息,所有状态信息都存储在客户端,服务器的内存从庞大的状态信息中解放出来。而且现在即使一台服务器突然死机对客户的影响也微乎其微,因为另一台服务器可以马上代替它的位置,而不需要考虑恢复状态信息。更多的缓存也变成可能,而之前由于服务器有状态,对同一个URI的请求可能导致完全不同的响应。总体结果是,网络的容错性和延展性都增强了。

  基于 REST 的 Web 服务的主要特征之一是以遵循RFC 2616 定义的协议的方式显式使用 HTTP 方法。例如,HTTP GET 被定义为数据产生方法,旨在由客户端应用程序用于检索资源以从 Web 服务器获取数据,或者执行某个查询并预期 Web 服务器将查找某一组匹配资源然后使用该资源进行响应。

  REST 要求开发人员显式地使用HTTP 方法,并且使用方式与协议定义一致。这个基本 REST 设计原则建立了创建、读取、更新和删除(create, read, update, and delete,CRUD)操作与 HTTP 方法之间的一对一映射。 根据此映射:

  • 若要在服务器上创建资源,应该使用 POST 方法。
  • 若要检索某个资源,应该使用GET 方法。
  • 若要更改资源状态或对其进行更新,应该使用 PUT 方法。
  • 若要删除某个资源,应该使用DELETE 方法。

  许多 Web API 中所固有的一个令人遗憾的设计缺陷在于将 HTTP 方法用于非预期用途。例如,HTTP GET 请求中的请求 URI 通常标识一个特定的资源。或者,请求 URI 中的查询字符串包括一组参数,这些参数定义服务器用于查找一组匹配资源的搜索条件。至少,HTTP/1.1 RFC 是这样描述 GET 方法的。但是在许多情况下,不优雅的 Web API 使用 HTTP GET 来触发服务器上的事务性操作——例如,向数据库添加记录。在这些情况下,GET 请求 URI 属于不正确使用,或者至少不是以基于 REST 的方式使用。如果 Web API 使用 GET 调用远程过程,则应该类似如下:

GET /adduser?name=RobertHTTP/1.1

  这不是非常优雅的设计,因为上面的 Web 方法支持通过 HTTP GET 进行状态更改操作。换句话说,该 HTTP GET 请求具有副作用。如果处理成功,则该请求的结果是向基础数据存储区添加一个新用户——在此例中为 Robert。这里的问题主要在语义上。 Web 服务器旨在通过检索与请求 URI 中的路径(或查询条件)匹配的资源,并在响应中返回这些资源或其表示形式,从而响应 HTTP GET 请求,而不是向数据库添加记录。从该协议方法的预期用途的角度看,然后再从与 HTTP/1.1 兼容的 Web 服务器的角度看,以这种方式使用 GET 是不一致的。

  除了语义之外,GET 的其他问题在于,为了触发数据库中的记录的删除、修改或添加,或者以某种方式更改服务器端状态,它请求 Web 缓存工具(爬网程序)和搜索引擎简单地通过对某个链接进行爬网处理,从而意外地做出服务器端更改。克服此常见问题的简单方法是将请求 URI 上的参数名称和值转移到 XML 标记中。这样产生的标记是要创建的实体的 XML 表示形式,可以在 HTTP POST 的正文中进行发送,此 HTTP POST 的请求 URI 是该实体的预期父实体(请参见代码 1 和 2):

  代码 1. 之前

GET /adduser?name=RobertHTTP/1.1

  代码 2. 之后

POST /users HTTP/1.1
Host: myserver
Content-Type:application/xml
<?xmlversion="1.0"?>
<user>
   <name>Robert</name>
</user>

  上述方法是基于REST 的请求的范例:正确使用 HTTP POST 并将有效负载包括在请求的正文中。在接收端,可以通过将正文中包含的资源添加为请求 URI 中标识的资源的从属资源,从而处理该请求;在此例下,应该将新资源添加为 /users 的子项。 POST 请求中指定的这种新实体与其父实体之间的包含关系类似于某个文件从属于其父目录的方式。客户端设置实体与其父实体之间的关系,并在 POST 请求中定义新实体的 URI。

  然后客户端应用程序可以使用新的 URI 获取资源的表示形式,并至少逻辑地指明该资源位于 /users 之下,如代码 3 所示。

  代码 3. HTTPGET 请求

GET /users/Robert HTTP/1.1
Host: myserver
Accept: application/xml

  以这种方式使用 GET是显式的,因为 GET 仅用于数据检索。 GET 是应该没有副作用的操作,即所谓的等幂性属性。

  当支持通过 HTTPGET 执行更新操作时,也需要应用类似的 Web 方法重构,如代码 4 所示。

  代码 4. 通过 HTTP GET 进行更新

GET/updateuser?name=Robert&newname=Bob HTTP/1.1

  这更改了资源的name 特性(或属性)。虽然可以将查询字符串用于此类操作,代码 4 就是一个简单的例子,但是在用于较复杂的操作时,这种将查询字符串作为方法签名的模式往往会崩溃。由于您的目标是显式使用 HTTP 方法,鉴于上述的相同原因(请参见代码 5),更符合 REST 的方法是发送 HTTP PUT 请求以更新资源,而不是发送 HTTP GET。

  代码 5. HTTPPUT 请求

PUT /users/Robert HTTP/1.1
Host: myserver
Content-Type:application/xml
<?xmlversion="1.0"?>
<user>
   <name>Bob</name>
</user>

  使用 PUT 取代原始资源可以提供更清洁的接口,这样的接口与 REST 的原则以及与 HTTP 方法的定义一致。代码 5 中的 PUT 请求是显式的,因为它通过在请求 URI 中标识要更新的资源来指向该资源,并且它在 PUT 请求的正文中将资源的新表示形式从客户端传输到服务器,而不是在请求 URI 上将资源属性作为参数名称和值的松散集合进行传输。代码 5 还具有将资源从 Robert 重命名为 Bob 的效果,这样做会将其 URI 更改为 /users/Bob。在 REST Web 服务中,使用旧的 URI 针对该资源的后续请求会产生标准的 404 Not Found 错误。

  作为一般设计原则,通过在 URI 中使用名词而不是动词,对于遵循有关显式使用 HTTP 方法的 REST 指导原则是有帮助的。在基于 REST 的 Web 服务中,协议已经对动词(POST、GET、PUT 和 DELETE)进行了定义。在理想的情况下,为了保持接口的通用化,并允许客户端明确它们调用的操作,Web 服务不应该定义更多的动词或远程过程,例如 /adduser 或 /updateuser。这条通用设计原则也适用于 HTTP 请求的正文,后者旨在用于传输资源状态,而不是用于携带要调用的远程方法或远程过程的名称。

从软件体系结构的角度来分析,每个软件构架模式都含有组件和连接件这两个部分组成。而在REST模式中,组件和连接件是以下部分组成:
组件:调用端(浏览器、客户端等等),REST服务器端
连接件:HTTP协议
  我们使用REST模式,又有哪些优点呢?

  1. 可以利用缓存Cache来提高响应速度
  2. 通讯本身的无状态性可以让不同的服务器处理一系列请求中的不同请求,提高服务器的扩展性
  3. 浏览器即可做客户端,简化软件开发的需求
  4. 相对于其他叠加的HTTP协议之上的机制,Rest的软件依赖性更小
  5. 不需要额外的资源发现机制
  6. 在软件技术演进中的长期的兼容性更好
  自然还有缺点:

  1. HTTP是一个同步的请求响应式协议。这意味着它不能天然地支持服务端发起的通知(点对点),但这又是经常需要的。
  2. SSL/TLS是REST唯一的安全机制
  3. HTTP的动词太少,尤其对于需要做点对点交互的场景

你可能感兴趣的:(REST模式)