理解RESTful:理论与最佳实践
什么是 REST
REST 一词,是由 HTTP 协议的主要设计者 Roy Fielding 在他 2000 年的博士论文中提出的。
论文地址:https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
在这篇论文中,Roy Fielding 阐述了一种 Web 软件的架构风格(Architectural Style),并将其命名为 REST,即 Representational State Transfer(表征性状态转移) 的缩写。
论文中描述了 REST 的六大软件工程原则,符合这些原则的架构被称为 RESTful 架构。
-
C/S架构。
-
无状态:服务端不记录客户端的状态,客户端的每次请求中都必须包含充分的信息,以便于服务端能够识别客户端的状态。
-
统一的接口
-
以资源为基础,使用 URI 来标识资源,请求的目标对象皆为资源,每个资源都可以通过 URI 访问到。 -
通过资源的表现层来操作资源。资源的表现层即资源的某种表示形式,比如数据库中的一条记录, 它的表现层可以是一段 JSON 数据,也可以是一段 HTML 数据,资源的表现层并不代表资源本身。当客户端请求 GET 一个资源时,服务端会将该资源的表现层返回给客户端。
-
-
可缓存:客户端允许缓存服务端响应的内容。
-
系统分层:在终端服务器与客户端之间允许存在中间层(如代理服务器)。
-
按需编码(可选):服务端可以通过给客户端返回一段功能代码(如 Javascript 代码)让客户端来执行,从而实现某些特定的功能。
作为 HTTP 协议的主要设计者,Roy Fielding 提出的 REST 架构风格恰恰是对 HTTP 协议的提倡与使用指导。HTTP 协议本身是一种面向资源的应用层协议,但是开发者们对 HTTP 的使用方式并不统一,很多 Web 服务的开发者们都并没有完全把 HTTP 当作应用层协议,而只是把它当做传输层协议来用,然后在 HTTP 之上又建立起了自己的应用层协议。这是 Roy Fielding 不希望看到的。
他倡导开发者们充分利用 HTTP 协议的特性(例如使用 HTTP Method 来指定对目标资源的操作)、遵从 HTTP 设计思想来开发 Web 服务。
从某种意义上来说,REST 架构风格就是遵从了 HTTP 设计思想的 Web 架构风格。
什么是 RESTful
RESTful 是 “REST” 的形容词形式,即“符合 REST 架构风格的”。符合 REST 架构风格的架构称为 RESTful 架构;符合 REST 架构风格的 Web 服务称为 RESTful Web 服务;符合 REST 架构风格的 Web API 称为 RESTful API。
Richardson 成熟度模型
Python 爬虫库 BeautifulSoup 的作者 Leonard Richardson 提出了一个成熟度模型,该模型把 RESTful Web 服务按照成熟度划分成 4 个层次:
-
第一个层次(Level 0)的 Web 服务只是使用了 HTTP 作为传输方式,不能算是 RESTful 服务。 -
第二个层次(Level 1)的 Web 服务引入了资源的概念,使用 URI 来标识资源。 -
第三个层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的操作结果。 -
第四个层次(Level 3)的 Web 服务使用 HATEOAS(超媒体作为应用程序状态的引擎),HATEOAS 意味着资源的表现层为超媒体,超媒体是指包含指向其他资源链接的媒体,例如包含链接字段的 JSON 文本等。当客户端访问某个资源时,服务端给客户端返回的数据中,除了包含目标资源自身的数据之外,还包含与之相关的其他资源的链接,客户端可以根据这些链接来发现其他资源。
RESTful API 设计最佳实践
-
URI 中尽量使用名词,原则上不使用动词,即 URI 仅用作对资源的标识。
-
通过 HTTP Method 来指定对资源的操作。如 GET 表示获取资源、POST 表示新建资源、PUT 表示全局更新资源(需要在请求体中包含完整的目标资源的表现层,因而不推荐使用),PATCH 表示局部更新资源,DELETE 表示删除资源。
-
URI 中的名词一般采用复数形式,表示某类资源的集合,如
/tickets
表示全部 ticket 的集合。 -
如果要表示集合中的单个资源,就在后面拼接这个资源的ID。如
/tickets/12
,表示 ID 为12的那个 ticket。 -
使用QueryString筛选集合中的元素,如
/tickets?status=1&sum>=100
,表示状态为1且金额>=100的 ticket 的集合。 -
通过 URI 的层层递进来建立资源的父子关系,如
/tickets/12/collections/3
,表示 ID 为12的那个 ticket 下的 ID 为3的 collection。 -
使用形容词来定制对某类资源的查询结果,如
/tickets/recently_closed
表示最近关闭的 ticket 的集合,/tickets/a_specialized
表示专门给a定制的 ticket 的集合。 -
为了支持复杂查询,建议提供
/queries
,当客户端需要传递的参数过多时,允许客户端POST /queries
,将查询参数放在请求体中传递过去,/queries
服务负责将请求体映射成一个 query 实体并写入数据库,然后返回 query_id。客户端拿到 query_id 后再GET /tickets?query_id=111
。 -
关于分页,HTTP 推荐将分页信息放在
Link
响应头中,参考 GitHub API 的设计,如下:Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",<https://api.github.com/user/repos?page=2&per_page=100>; rel="pre",<https://api.github.com/user/repos?page=1&per_page=100>; rel="first",<https://api.github.com/user/repos?page=50&per_page=100>; rel="last",
补充:HTTP 状态码及说明
-
101 Switching Protocols:表示需要切换网络协议,此时客户端应当断开 HTTP 连接,使用指定的协议重新与服务端建立连接。
-
200 OK:表示一切正常。
-
201 Created :表示资源已成功创建,新资源的 URL 位于
Location
响应头中,用户可以选择在需要的时候访问它。 -
301 Moved Permanently:表示目标资源被永久转移,新的 URL 位于
Location
响应头中,此时客户端应当对新的 URL 发起请求。 -
302 Found:表示目标资源被临时转移,新的 URL 位于
Location
响应头中,此时客户端应当对新的 URL 发起请求。 -
303 See Other:表示请求已被处理,但未返回处理结果,此时客户端应当请求另一个资源来获取处理结果,该资源的 URL 位于
Location
响应头中。 -
307 Temporary Redirect:表示请求尚未被处理,是因为请求的资源不在本地,而在另一个 URL 处,客户端应当对那个 URL 发起请求,该 URL 位于
Location
响应头中。307 与 303 的区别:对于 GET 请求来说,307 与 303 没有区别,对于 POST、PUT、DELETE 请求来说,它们的区别在于,307 说明请求的操作尚未执行,而 303 说明请求的操作已经执行过了。
-
400 Bad Request:表示用户发起的请求有问题,服务端无法处理该请求。
-
401 Unauthorized:表示用户对该资源的访问尚未得到授权。
-
403 Forbidden:表示用户无权访问该资源。
-
404 Not Found:表示目标资源不存在。
-
415 Unsupported Media Type:表示请求体的媒体类型与服务端所期望的不符。
-
429 Too Many Requests:表示用户请求的次数过多,超出了服务端的限速阈值。
-
500 Internal Server Error:表示服务端内部出现异常。
-
502 Bad Gateway:表示客户端代理方面出现异常。
-
503 Service Unavailable:表示服务端因繁忙或故障而拒绝本次服务,并通过响应头Retry-After告知客户端何时可以重试。