认识 RESTful
REST 称为表现层状态转化(或者称为具象状态传输)。
在前后端分离的应用模式里,后端 API 接口如何定义?例如对于后端数据库中保存了商品的信息,前端可能需要对商品数据进行增删改查,那相应的每个操作后端都需要提供一个 API 接口:
POST /add-goods增加商品POST /delete-goods删除商品POST /update-goods修改商品GET /get-goods查询商品信息
对于接口的请求方式与路径,每个后端开发人员可能都有自己的定义方式,风格迥异。API 的 RESTful 设计风格,被广大开发人员接受并使用。
RESTful 的内容
RESTful 是一种开发理念。
RESTful 风格与非 RESTful 风格的区别
RESTful 的特点:URL 简洁,将参数通过 URL 传到服务器。
什么是 RESTful 架构
要理解 RESTful 架构,需要理解 Representational State Transfer 这三个单词的意思。
如果一个架构符合 REST 原则,就称它为 RESTful 架构。
- 具象(Representational):就是指表现层,要表现的对象也就是”资源”。什么是资源呢?网站就是资源共享的东西,客户端(浏览器)访问 Web 服务器,所获取的就叫资源,比如 HTML、txt、JSON、图片、视频等
- 表现(State):比如文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现
浏览器通过 URL 确定一个资源,但是如何确定它的具体表现形式呢?应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述。
- 状态转换(Transfer):就是客户端和服务器互动的一个过程,在这个过程中,势必涉及到数据和状态的变化,这种变化叫做状态转换
互联网通信协议 HTTP 协议,客户端访问必然使用 HTTP 协议,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。
HTTP 协议实际上含有 4 个表示操作方式的动词,分别是 GET、POST、PUT、DELETE,它们分别对应四种操作。
而且 HTTP 协议是一种无状态协议,这样就必须把所有的状态都保存在服务器端。 因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。
RESTful 架构总结
综合上面的解释,RESTful 架构就是:
- 每一个 URL 代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现”表现层状态转化”
设计方法
应该从以下几个方面去思考设计:
- 域名
- 版本(Versioning)
- 路径(Endpoint)
- HTTP 动词
- 过滤信息(Filtering)
- 状态码(Status Codes)
- 错误处理(Error handling)
- 返回结果
- 超媒体(Hypermedia API)
1. 域名
应该尽量将 API 部署在专用域名之下:
1 | https://api.example.com |
如果确定 API 很简单,不会有进一步扩展,可以考虑放在主域名下:
1 | https://example.org/api/ |
2. 版本(Versioning)
应该将 API 的版本号放入 URL:
1 | http://www.example.com/app/1.0/foo |
另一种做法是将版本号放在 HTTP 头信息中,但不如放入 URL 方便和直观。GitHub 采用这种做法。
因为不同的版本可以理解成同一种资源的不同表现形式,所以应该采用同一个 URL,版本号可以在 HTTP 请求头信息的 Accept 字段中进行区分:
1 | Accept: vnd.example-com.foo+json; version=1.0 |
3. 路径(Endpoint)
路径又称”终点”(Endpoint),表示 API 的具体网址,每个网址代表一种资源(Resource)。
(1) 资源作为网址,只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应。
(2) API 中的名词应该使用复数,无论子资源或者所有资源。
不好的例子:
1 | /getProducts |
对于一个简洁结构,应该始终用名词。此外,利用 HTTP 方法可以分离网址中的资源名称的操作:
1 | GET /products :返回所有产品清单 |
获取产品的 API 可以这样定义:
1 | 获取单个产品:GET /products/1 |
4. HTTP 动词
对于资源的具体操作类型,由 HTTP 动词表示。
常用的 HTTP 动词(括号里是对应的 SQL 命令):
| 动词 | 说明 | SQL 对应 |
|---|---|---|
| GET | 从服务器取出资源(一项或多项) | SELECT |
| POST | 在服务器新建一个资源 | CREATE |
| PUT | 在服务器更新资源(客户端提供改变后的完整资源) | UPDATE |
| DELETE | 从服务器删除资源 | DELETE |
不常用的 HTTP 动词:
| 动词 | 说明 |
|---|---|
| PATCH | 在服务器更新资源(客户端提供改变的属性) |
| HEAD | 获取资源的元数据 |
| OPTIONS | 获取信息,关于资源的哪些属性是客户端可以改变的 |
示例:
1 | GET /zoos :列出所有动物园 |
5. 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。
常见的参数:
1 | ?limit=10 :指定返回记录的数量 |
参数的设计允许存在冗余,即允许 API 路径和 URL 参数偶尔有重复。比如 GET /zoos/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
6. 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些:
| 状态码 | 说明 | 适用动词 |
|---|---|---|
| 200 OK | 服务器成功返回用户请求的数据 | GET |
| 201 CREATED | 用户新建或修改数据成功 | POST/PUT/PATCH |
| 202 Accepted | 一个请求已经进入后台排队(异步任务) | * |
| 204 NO CONTENT | 用户删除数据成功 | DELETE |
| 400 INVALID REQUEST | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作 | POST/PUT/PATCH |
| 401 Unauthorized | 用户没有权限(令牌、用户名、密码错误) | * |
| 403 Forbidden | 用户得到授权,但是访问是被禁止的 | * |
| 404 NOT FOUND | 用户发出的请求针对的是不存在的记录 | * |
| 406 Not Acceptable | 用户请求的格式不可得 | GET |
| 410 Gone | 用户请求的资源被永久删除,且不会再得到 | GET |
| 422 Unprocesable entity | 创建一个对象时,发生一个验证错误 | POST/PUT/PATCH |
| 500 INTERNAL SERVER ERROR | 服务器发生错误,用户将无法判断发出的请求是否成功 | * |
7. 错误处理(Error handling)
如果状态码是 4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息中将 error 作为键名,出错信息作为键值即可:
1 | { |
8. 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范:
| 操作 | 返回结果 |
|---|---|
| GET /collection | 返回资源对象的列表(数组) |
| GET /collection/resource | 返回单个资源对象 |
| POST /collection | 返回新生成的资源对象 |
| PUT /collection/resource | 返回完整的资源对象 |
| PATCH /collection/resource | 返回完整的资源对象 |
| DELETE /collection/resource | 返回一个空文档 |
9. 超媒体(Hypermedia API)
RESTful API 最好做到 Hypermedia(即返回结果中提供链接,连向其他 API 方法),使得用户不查文档,也知道下一步应该做什么。
比如 GitHub 的 API 就是这种设计,访问 api.github.com 会得到一个所有可用 API 的网址列表:
1 | { |
如果想获取当前用户的信息,应该去访问 api.github.com/user,然后就得到了下面结果:
1 | { |
服务器给出了提示信息,以及文档的网址。
提示
服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML。