RESETful API 设计规范
RESTful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计
- http请求方法
- RESTful API 设计规范
关于「能愿动词」的使用
为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:
必须 (MUST):绝对,严格遵循,请照做,无条件遵守;一定不可 (MUST NOT):禁令,严令禁止;应该 (SHOULD):强烈建议这样做,但是不强求;不该 (SHOULD NOT):强烈不建议这样做,但是不强求;可以 (MAY)和可选 (OPTIONAL):选择性高一点,在这个文档内,此词语使用较少;
参见:RFC 2119
域名
API 的根入口点应尽可能保持足够简单,这里有两个常见的 URL 根例子:
https://api.example.com/*
https://example.com/api/*如果你的应用很庞大或者你预计它将会变的很庞大,那 应该 将 API 放到子域下(api.example.com)。这种做法可以保持某些规模化上的灵活性。
客户端请求
API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以,客户端希望服务器回应的 HTTP 头的Content-Type属性要设为application/json。
GET /users/2 HTTP/1.1 Accept: application/json Content-Type: application/json
版本控制
所有的 API 必须保持向后兼容,你 必须 在引入新版本 API 的同时确保旧版本 API 仍然可用。所以 应该 为其提供版本支持。
目前比较常见的两种版本号形式:
在 URL 中嵌入版本编号
https://api.example.com/v1/* https://api.example.com/v2/*
将版本号放在 HTTP Header 头中 通过媒体类型来指定版本信息
Accept: application/vnd.example.com.v1+json
HTTP 动词
HTTP 请求动词通常就是五种方法,对应 CRUD 操作。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
针对每一个端点来说,下面列出所有可行的 HTTP 动词和端点的组合
用 URL 定位资源
| 请求方法 | URL | 描述 |
|---|---|---|
| GET | /zoos | 列出所有的动物园(ID和名称,不要太详细) |
| POST | /zoos | 新增一个新的动物园 |
| GET | /zoos/{zoo} | 获取指定动物园详情 |
| PUT | /zoos/{zoo} | 更新指定动物园(整个对象) |
| PATCH | /zoos/{zoo} | 更新动物园(部分对象) |
| DELETE | /zoos/{zoo} | 删除指定动物园 |
| GET | /zoos/{zoo}/animals | 检索指定动物园下的动物列表(ID和名称,不要太详细) |
| GET | /animals | 列出所有动物(ID和名称)。 |
| POST | /animals | 新增新的动物 |
| GET | /animals/{animal} | 获取指定的动物详情 |
| PUT | /animals/{animal} | 更新指定的动物(整个对象) |
| PATCH | /animals/{animal} | 更新指定的动物(部分对象) |
| GET | /animal_types | 获取所有动物类型(ID和名称,不要太详细) |
| GET | /animal_types/{type} | 获取指定的动物类型详情 |
| GET | /employees | 检索整个雇员列表 |
| GET | /employees/{employee} | 检索指定特定的员工 |
| GET | /zoos/{zoo}/employees | 检索在这个动物园工作的雇员的名单(身份证和姓名) |
| POST | /employees | 新增指定新员工 |
| POST | /zoos/{zoo}/employees | 在特定的动物园雇佣一名员工 |
| DELETE | /zoos/{zoo}/employees/{employee} | 从某个动物园解雇一名员工 |
Restful 端点的,应该 模仿上表的方式来定义端点。资源过滤
如果记录数量很多,服务器不可能都将它们返回给用户。API应该 提供参数,过滤返回结果。下面是一些常见的参数。- ?page=10:指定返回记录的数量
- ?per_page=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?status=closed:指定筛选条件
所有 URL 参数 必须 是全小写,必须 使用下划线类型的参数形式。
必须 固定为 page、per_page经常使用的、复杂的查询 应该 标签化,降低维护成本。如
GET /trades?status=closed&sort=sortby=name&order=asc
返回码
20x
200 OK
201 Created
对创建新资源的 POST 操作进行响应。应该带着指向新资源地址的 Location 头
202 Accepted
服务器接受了请求,但是还未处理,响应中应该包含相应的指示信息,告诉客户端该去哪里查询关于本次请求的信息
204 No Content
对不会返回响应体的成功请求进行响应(比如 DELETE 请求)
3xx 重定向
40x 客户端错误
400 Bad Request
请求异常,比如请求中的body无法解析
401 Unauthorized
没有进行认证或者认证非法或失效
403 Forbidden
服务器已经理解请求,但是拒绝执行它
404 Not Found
该状态码表示用户请求的资源不存在,如
- 获取不存在的用户信息 (get /users/9999999)
- 访问不存在的端点
都 必须 返回该状态码,若该资源已永久不存在,则 应该 返回 410 响应。
405 Method Not Allowed
所请求的 HTTP 方法不允许当前认证用户访问
409 Gonfilct
该状态码表示因为请求存在冲突无法处理。
如通过手机号码提供注册功能的 API,当用户提交的手机号已存在时,必须 返回此状态码。
410 Gone
表示当前请求的资源已永久不存在。当调用老版本 API 的时候很有用
413 Request Entity Too Large
该状态码表示服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。
此种情况下,服务器可以关闭连接以免客户端继续发送此请求。如果这个状况是临时的,服务器 应该 返回一个 Retry-After 的响应头,以告知客户端可以在多少时间以后重新尝试。
414 Request-URI Too Long
该状态码表示请求的 URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。
415 Unsupported Media Type
通常表示服务器不支持客户端请求首部 Content-Type 指定的数据格式。如在只接受 JSON 格式的 API 中放入 XML 类型的数据并向服务器发送,都 应该 返回该状态码。
该状态码也可用于如:只允许上传图片格式的文件,但是客户端提交媒体文件非法或不是图片类型,这时 应该 返回该状态码:
HTTP/1.1 415 Unsupported Media Type
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 12:09:40 GMT
Connection: keep-alive
{"error_code":41500,"message":"不允许上传的图片格式"}422 Unprocessable Entity
用来表示校验错误
{
"message": "422 Unprocessable Entity",
"errors": {
"name": [
"姓名 必须为字符串。"
]
},
"status_code": 422
}429 Too Many Requests
该状态码表示用户请求次数超过允许范围。如 API 设定为 60次/分钟,当用户在一分钟内请求次数超过 60 次后,都 应该 返回该状态码。并且也 应该 在响应首部中加上下列头部:
X-RateLimit-Limit: 10 请求速率(由应用设定,其单位一般为小时/分钟等,这里是 10次/5分钟) X-RateLimit-Remaining: 0 当前剩余的请求数量 X-RateLimit-Reset: 1529839462 重置时间 Retry-After: 120 下一次访问应该等待的时间(秒)
列子
必须 为所有的 API 设置 Rate Limit 支持。
50x 服务器错误
500 Internal Server Error
503 Service Unavailable
数据响应格式
错误格式
对于错误数据,默认使用如下结构:
'message' => ':message', // 错误的具体描述 'errors' => ':errors', // 参数的具体错误描述,422 等状态提供 'code' => ':code', // 业务自定义的异常码 'status_code' => ':status_code', // http状态码 'debug' => ':debug', // debug 信息,非生产环境提供
422错误码显示
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"message": "422 Unprocessable Entity",
"errors": {
"username": [
"姓名 必须为字符串。"
"姓名 必须介于 4 - 18 个字符之间"
],
"phone": [
"手机号码 格式不正确。"
]
},
"status_code": 422
}403错误码显示
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"message": "您无权访问该订单",
"status_code":"403"
}429错误码显示
HTTP/1.1 429 Too Many Requests
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1529839462
Retry-After: 290
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 11:19:32 GMT
Connection: keep-alive
{
"message":"You have exceeded your rate limit.",
"status_code":429
}正确输出显示
分页显示
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}