Skip to content

Latest commit

 

History

History
executable file
·
720 lines (528 loc) · 21.7 KB

rest_style_guide.md

File metadata and controls

executable file
·
720 lines (528 loc) · 21.7 KB

Code of Conduct

版本管理

变更者 变更时间 变更内容 版本 备注
Hiro 2022-02-20 初稿 v0.1
Hiro 2022-04-20 初稿 v0.2 新增更多例子

语义解释

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

必须 (MUST): 绝对,严格遵循,请照做,无条件遵守.

一定不可 (MUST NOT): 禁令,严令禁止.

应该 (SHOULD) : 强烈建议这样做,但是不强求.

不该 (SHOULD NOT): 强烈不建议这样做,但是不强求.

协议

API 与客户端通讯协议主要包含 http 和 https,应该 使用 HTTPS 协议.

API 根域名

API 的根入口点应尽可能保持足够简单,这里有两个常见的 URL 根例子:

  • api.inspiration.com/*
  • inspiration.com/api/*

如果你的应用很庞大或者你预计它将会变的很庞大,那 应该 将API放到子域下(api.inspiration.com)。这种做法可以保持某些规模化上的灵活性。

版本管理

所有的 API 必须保持向后兼容,你 必须 在引入新版本 API 的同时确保旧版本 API 仍然可用。所以 应该 为其提供版本支持。

目前比较常见的版本号形式:

1. 在 URL 中嵌入版本编号

api.inspiration.com/v1/*

这种做法是版本号直观、易于调试;

2. 将版本号放在 HTTP Header 头中

header: v1

3. 使用自定义请求标头进行版本控制

Accept-version: v1

URI格式

URI语法由组件的层次结构序列组成:

    https://spacex.com:8015/v1/over/there?name=ferret#nose
    \___/   \_______________/\_________/\_________/\__/
    |           |            |            |        |
    scheme     authority       path        query   fragment

端点就是指向特定资源或资源集合的 URL。在端点的设计中,你 必须 遵守 下列约定:

URL 必须以字母开头,并且只能使用小写字母

✅ GOOD
https://api.inspiration.com/health

❌ BAD
https://api.inspiration.com/getUser
https://api.inspiration.com/get_user

URL 路径中的文字/表达式应使用连字符(-)分隔

https://api.inspiration.com/how-to-design-better-apis

URL 查询字符串中的文字/表达式应使用下划线(_)分隔,

  • 参数必须以字母开头,并且应全部小写。
  • 只能使用字母字符,数字和下划线(_)字符,
  • 查询参数应该是可选的。
https://api.inspiration.com/health?api_version=1.0

URL 中资源(resource)的命名 必须 是名词,并且优先 应该 是复数形式

https://api.inspiration.com/books

URL 资源集合中的单个资源可以直接存在于集合URI的下面。

  https://api.inspiration.com/invoices/{invoice_id}

子资源集合可以直接存在于单个资源之下。这应该传达与另一个资源集合的关系

https://api.inspiration.com/invoices/{invoice_id}/items`

子资源的单个资源可以存在,但应避免使用顶级资源

❌ BAD
`: https://api.inspiration.com/invoices/{invoice_id}/items/{item_id}

✅ GOOD
https://api.inspiration.com/invoice-items/{invoice_item_id}`

不得有两个资源标识符,一个接一

https://api.inspiration.com/payments/payments/12345/102030

缓存友好的API

- 在极少数情况下,资源需要高度可缓存(通常是数据变化很小),可以使用查询参数,而不是POST+请求主体.
- 至于POST会做出反应不可缓存,GET最好在这些情况下。这是唯一可能需要查询参数的情况.

HTTP 动词

Http Method

对于资源的具体操作类型,由 HTTP 动词表示。常用的 HTTP 动词有下面五个(括号里是对应的 SQL 命令)。

GET(SELECT):从服务器取出资源(一项或多项)。

POST(CREATE):在服务器新建一个资源。

PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源, 非幂等操作)此方法应该返回状态代码, 204因为在大多数情况下,由于请求是要更新资源并且已成功更新,因此无需返回任何内容。

PATCH(UPDATE):在服务器更新资源(更新某些字段, 幂等操作)(客户端提供改变的属性)。

DELETE(DELETE):从服务器删除资源 幂等操作

其中

1 删除资源 必须DELETE 方法

2 创建新的资源 必须 使用 POST 方法

3 更新资源 应该 使用 PUT 方法

4 获取资源信息 必须 使用 GET 方法

针对每一个端点来说,下面列出所有可行的 HTTP 动词和端点的组合

请求方法 URL 描述
GET /zoos 列出所有的动物园
POST /zoos 新增一个新的动物园
GET /zoos/{zoo} 获取指定动物园详情
PUT /zoos/{zoo} 更新指定动物园(整个对象)
PATCH /zoos/{zoo} 更新动物园(部分对象)
DELETE /zoos/{zoo} 删除指定动物园
GET /zoos/{zoo}/animals 检索指定动物园下的动物列表
GET /animals 列出所有动物(ID和名称)。
POST /animals 新增新的动物
GET /animals/{animal} 获取指定的动物详情
PUT /animals/{animal} 更新指定的动物(整个对象)
PATCH /animals/{animal} 更新指定的动物(部分对象)
GET /animal-types 获取所有动物类型
GET /animal-types/{type} 获取指定的动物类型详情
GET /employees 检索整个雇员列表
GET /employees/{employee} 检索指定特定的员工
GET /zoos/{zoo}/employees 检索在这个动物园工作的雇员的名单
POST /employees 新增指定新员工
POST /zoos/{zoo}/employees 在特定的动物园雇佣一名员工
DELETE /zoos/{zoo}/employees/{employee} 从某个动物园解雇一名员工

Headers

  • HTTP标头以 camelCase + 连字符的语法编写,例如 Foo-Request-Id or Request-Id.

  • 自定义头信息不是必须的.

返回

国内普通使用的返回格式,非必须.

{
    "code": 0,  # 状态码
    "msg": "OK",  # 提示信息
    "data": {}  # 主体数据
}

Response 响应

所有的 API 响应,必须 遵守 HTTP 设计规范,必须 选择合适的 HTTP 状态码

下表列举了常见的 HTTP 状态码

状态码 描述
1xx 代表请求已被接受,需要继续处理
2xx 请求已成功,请求所希望的响应头或数据体将随此响应返回
3xx 重定向
4xx 客户端原因引起的错误
5xx 服务端原因引起的错误

只有来自客户端的请求被正确的处理后才能返回 2xx 的响应,所以当 API 返回 2xx 类型的状态码时,前端 必须 认定该请求已处理成功。 必须强调的是,所有 API 一定不可 返回 1xx 类型的状态码。当 API 发生错误时,必须 返回出错时的详细信息。

2、直接放入响应实体中;

HTTP/1.1 401 Unauthorized
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 10:02:59 GMT
Connection: keep-alive
{"error_code":40100,"message":"Unauthorized"}

考虑到易读性和客户端的易处理性,我们 必须 把错误信息直接放到响应实体中,并且错误格式 应该 满足如下格式:

{
  "message": "您查找的资源不存在",
  "error_code": 404001
}

其中错误码(error_code必须HTTP 状态码对应,也方便错误码归类,如:

HTTP/1.1 429 Too Many Requests
Server: nginx/1.16
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Connection: keep-alive
{"error_code":429001,"message":"你操作太频繁了"}
HTTP/1.1 403 Forbidden
Server: nginx/1.16
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Connection: keep-alive
{"error_code":403002,"message":"用户已禁用"}

下面详细列举了各种情况 API 的返回说明。

200 ok

200 状态码是最常见的 HTTP 状态码,在所有 成功GET 请求中,必须 返回此状态码。HTTP 响应实体部分 必须 直接就是数据,不要做多余的包装。

错误示例:

HTTP/1.1 200 ok
Content-Type: application/json
Server: example.com
{
    "user": {
        "id":1,
        "nickname":"fwest",
        "username": "example"
    }
}

正确示例:

1、获取单个资源详情

{
  "id": 1,
  "username": "godruoyi",
  "age": 88
}

2、获取资源集合

[
  {
    "id": 1,
    "username": "godruoyi",
    "age": 88
  },
  {
    "id": 2,
    "username": "foo",
    "age": 88
  }
]

3、额外的媒体信息

{
  "data": [
    {
      "id": 1,
      "avatar": "https://lorempixel.com/640/480/?32556",
      "nickname": "fwest",
      "last_logined_time": "2018-05-29 04:56:43",
      "has_registed": true
    },
    {
      "id": 2,
      "avatar": "https://lorempixel.com/640/480/?86144",
      "nickname": "zschowalter",
      "last_logined_time": "2018-06-16 15:18:34",
      "has_registed": true
    }
  ],
  "meta": {
    "pagination": {
      "total": 101,
      "count": 2,
      "per_page": 2,
      "current_page": 1,
      "total_pages": 51,
      "links": {
        "next": "http://api.example.com?page=2"
      }
    }
  }
}

其中,分页和其他额外的媒体信息,必须放到 meta 字段中。

201 Created

当服务器创建数据成功时,应该 返回此状态码。常见的应用场景是使用 POST 提交用户信息,如:

  • 添加了新用户
  • 上传了图片
  • 创建了新活动

等,都可以返回 201 状态码。需要注意的是,你可以选择在用户创建成功后返回新用户的数据

HTTP/1.1 201 Created
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 24 Jun 2018 09:13:40 GMT
Connection: keep-alive
{
    "id": 1,
    "avatar": "https:\/\/lorempixel.com\/640\/480\/?32556",
    "nickname": "fwest",
    "last_logined_time": "2018-05-29 04:56:43",
    "created_at": "2018-06-16 17:55:55",
    "updated_at": "2018-06-16 17:55:55"
}

也可以返回一个响应实体为空的 HTTP Response 如:

HTTP/1.1 201 Created
Server: nginx/1.11.9
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 24 Jun 2018 09:12:20 GMT
Connection: keep-alive

这里我们 应该 采用第二种方式,因为大多数情况下,客户端只需要知道该请求操作成功与否,并不需要返回新资源的信息。

202 Accepted

用于异步操作方法执行. 该状态码表示服务器已经接受到了来自客户端的请求,但还未开始处理。常用短信发送、邮件通知、模板消息推送等这类很耗时需要队列支持的场景中;

返回该状态码时,响应实体 必须 为空。

HTTP/1.1 202 Accepted
Server: nginx/1.11.9
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 24 Jun 2018 09:25:15 GMT
Connection: keep-alive

204 No Content

没有要返回的实体主体. 该状态码表示响应实体不包含任何数据,其中:

  • 在使用 DELETE 方法删除资源 成功 时,必须 返回该状态码
  • 使用 PUTPATCH 方法更新数据 成功 时,也 应该 返回此状态码
HTTP/1.1 204 No Content
Server: nginx/1.11.9
Date: Sun, 24 Jun 2018 09:29:12 GMT
Connection: keep-alive

3xx 重定向

所有 API 不该 返回 3xx 类型的状态码。因为 3xx 类型的响应格式一般为下列格式:

HTTP/1.1 302 Found
Server: nginx/1.11.9
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 09:41:50 GMT
Location: https://example.com
Connection: keep-alive

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="refresh" content="0;url=https://example.com"/>

    <title>Redirecting to https://example.com</title>
</head>
<body>
Redirecting to <a href="https://example.com">https://example.com</a>.
</body>
</html>

所有 API 一定不可 返回纯 HTML 结构的响应;若一定要使用重定向功能,可以 返回一个响应实体为空的 3xx 响应,并在响应头中加上 Location 字段:

HTTP/1.1 302 Found
Server: nginx/1.11.9
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 24 Jun 2018 09:52:50 GMT
Location: https://godruoyi.com
Connection: keep-alive

400 Bad Request

由于明显的客户端错误(例如,请求语法格式错误、无效的请求、无效的签名等),服务器 应该 放弃该请求。

当服务器无法从其他 4xx 类型的状态码中找出合适的来表示错误类型时,都 必须 返回该状态码。

HTTP/1.1 400 Bad Request
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 13:22:36 GMT
Connection: keep-alive
{"error_code":40000,"message":"无效的签名"}

401 Unauthorized

该状态码表示当前请求需要身份认证,以下情况都 必须 返回该状态码。

  • 未认证用户访问需要认证的 API
  • access_token 无效/过期

客户端在收到 401 响应后,都 应该 提示用户进行下一步的登录操作。

HTTP/1.1 401 Unauthorized
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
WWW-Authenticate: JWTAuth
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 13:17:02 GMT
Connection: keep-alive
{"message":"Token Signature could not be verified.","error_code": "40100"}

403 Forbidden

该状态码可以简单的理解为没有权限访问该请求,服务器收到请求但拒绝提供服务。

如当普通用户请求操作管理员用户时,必须 返回该状态码。

HTTP/1.1 403 Forbidden
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 13:05:34 GMT
Connection: keep-alive
{"error_code":40301,"message":"权限不足"}

404 Not Found

该状态码表示用户请求的资源不存在,如

  • 获取不存在的用户信息 (get /user/u3x3pd435)
  • 访问不存在的端点

必须 返回该状态码,若该资源已永久不存在,则 应该 返回 410 响应。

405 Method Not Allowed

当客户端使用的 HTTP 请求方法不被服务器允许时,必须 返回该状态码。

如客户端调用了 POST 方法来访问只支持 GET 方法的 API 该响应 必须 返回一个 Allow 头信息用以表示出当前资源能够接受的请求方法的列表。

HTTP/1.1 405 Method Not Allowed
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Allow: GET, HEAD
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 12:30:57 GMT
Connection: keep-alive
{"message":"405 Method Not Allowed","error_code": 40500}

406 Not Acceptable

API 在不支持客户端指定的数据格式时,应该返回此状态码。如支持 JSONXML 输出的 API 被指定返回 YAML 格式的数据时。

Http 协议一般通过请求首部的 Accept 来指定数据格式

408 Request Timeout

客户端请求超时时 必须 返回该状态码,需要注意的时,该状态码表示 客户端请求超时,在涉及第三方 API 调用超时时,一定不可 返回该状态码。

409 Confilct

该状态码表示因为请求存在冲突无法处理。如通过手机号码提供注册功能的 API,当用户提交的手机号已存在时,必须 返回此状态码。

HTTP/1.1 409 Conflict
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 12:19:04 GMT
Connection: keep-alive
{"error_code":40900,"message":"手机号已存在"}

410 Gone

404 类似,该状态码也表示请求的资源不存在,只是 410 状态码进一步表示所请求的资源已不存在,并且未来也不会存在。在收到 410 状态码后,客户端 应该 停止再次请求该资源。

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":"不允许上传的图片格式"}

429 Too Many Requests

该状态码表示用户请求次数超过允许范围。如 API 设定为 60次/分钟,当用户在一分钟内请求次数超过 60 次后,都 应该 返回该状态码。并且也 应该 在响应首部中加上下列头部:

X-RateLimit-Limit: 10 请求速率(由应用设定,其单位一般为小时/分钟等,这里是 10次/5分钟)
X-RateLimit-Remaining: 0 当前剩余的请求数量
X-RateLimit-Reset: 1529839462 重置时间
Retry-After: 120 下一次访问应该等待的时间(秒)

例如:

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.","error_code":42900}

必须 为所有的 API 设置 Rate Limit 支持。

500 Internal Server Error

该状态码 必须 在服务器出错时抛出,对于所有的 500 错误,都 应该 提供完整的错误信息支持,也方便跟踪调试。

503 Service Unavailable

该状态码表示服务器暂时处理不可用状态,当服务器需要维护或第三方 API 请求超时/不可达时,都 应该 返回该状态码,其中若是主动关闭 API 服务,应该 在返回的响应首部加上 Retry-After 头部,表示多少秒后可以再次访问。

HTTP/1.1 503 Service Unavailable
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 10:56:20 GMT
Retry-After: 60
Connection: keep-alive
{"error_code":50300,"message":"服务维护中"}

其他 HTTP 状态码请参考Http-StatusCode

分页

// Request => GET /users?page_number=1&page_size=15&after_id=20 

// Response <= 200 OK
{
    "page_number": 1,
    "page_size": 15,
    "count": 378,
    "data": [
        // resources
    ],
    "total_pages": 26,
    "has_previous_page": true,
    "has_next_page": true
}

其他实践

提供 health check

应该提供一个端点(例如get /health)用于确定服务是否健康。此端点可以通过其他应用程序调用, 例如负载均衡器以在服务中断的情况下采取行动。

ISO8601 UTC日期格式

必须 UTC日期格式

{
    "published_at": "2022-04-18T15:15:52.071106",
    "created_at": "2022-04-18T07:19:35.920098+08:00"  # 东八区
}

更新优先考虑 PATCH

PATCH请求是对资源的部分更新,应该优先考虑. 而PUT是完全替代现有的资源 允许任务字段不受限制的任意更新是危险的操作.

加载更多资源

允许通过expend查询加载关联数据,避免多次往返请求.

// Request => GET /users/T9hoBuuTL4?expand=orders&expand=orders.items

// Response <= 200 OK
{
  "id": "T9hoBuuTL4",
  "email": "[email protected]",
  "name": "John Doe",
  "orders": [
    {
      "id": "Hy3SSXU1PF",
      "items": [
        {
          "name": "API course"
        },
        {
          "name": "iPhone 14"
        }
      ]
    },
    {
      "id": "bx1zKmJLI6",
      "items": [
        {
          "name": "SaaS subscription"
        }
      ]
    }
  ]
}

Credit

  1. PayPal
  2. Microsoft
  3. Stripe