HTTP API 编程设计规范

发布于 2022-06-29 13:04:23 字数 9482 浏览 966 评论 0

英文原文: HTTP API Design Guide

介绍

本指南描述了一套有关 HTTP+JSON API 的设计实践, 原始内容提取自 Heroku 平台 API 的工作。

本指南是对 API 的补充,也是 Heroku 新的内部API的指南. 我们希望引起Heroku之外的API设计者的兴趣。

这里我们的目标是一致的,专注于业务逻辑而避免脱节的设计. 我们就是要寻找一个良好的,一致的,文档优良的方式来设计 API,而没必要是唯一理想的方式。

我们假定你熟悉 HTTP+JSON API 的一些基础,不会再指南中涵盖所有基础性的东西。

返回适当的状态码

对于每一种响应返回适当的HTTP状态码. 成功的响应应该根据下面的指南编码:

  • 200:GET调用请求成功, 以及DELETE 或者 PATCH 调用同步完成
  • 201:同步完成的 POST 调用请求成功
  • 202:请求接受一个将会被同步处理的 POST,DELETE 或者 PATCH 调用
  • 206:GET 请求成功,但只有部分响应返回:见 上述有关范围的内容

请阅读指导有关用户错误和服务器错误情况的 状态码的HTTP 响应码文档

提供可用的完整资源

尽可能在响应中提供完整的资源描述(例如,带有所有属性的对象),总是在 200 和 201 响应中提供完整的资源,包括 PUT/PATCH 和 DELETE 请求,例如:

$ curl -X DELETE \  
  https://service.com/apps/1f9b/domains/0fd4

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
  "created_at": "2012-01-01T12:00:00Z",
  "hostname": "subdomain.example.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "updated_at": "2012-01-01T12:00:00Z"
}

202 响应不会包含完整的资源描述,例如:

$ curl -X DELETE \  
  https://service.com/apps/1f9b/dynos/05bd

HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}

接受请求中序列化的JSON

接受 PUT/PATCH/POST 请求中的序列化 JSON,作为表单编码数据的替代或者补充。这样就可以创建对称的 JSON 序列化响应,例如:

$ curl -X POST https://service.com/apps \
    -H "Content-Type: application/json" \
    -d '{"name": "demoapp"}'

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "name": "demoapp",
  "owner": {
    "email": "username@example.com",
    "id": "01234567-89ab-cdef-0123-456789abcdef"
  },
  ...
}

提供资源(UU)ID

默认给每一个资源都指定一个 id,除非你有更好的理由,不然就使用 UUID,不要使用在整个服务或者服务中其它资源那里不是全局唯一的 ID,特别是自增长的 ID。

用小写 8-4-4-4-12 格式生成 UUID,例如:

"id": "01234567-89ab-cdef-0123-456789abcdef"

提供标准的时间戳

默认为资源提供创建和更新的时间戳,例如:

{
  ...
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T13:00:00Z",
  ...}

这些时间戳可能对一些资源没啥用,如此则可以省去。

使用 ISO8601 中的 UTC 时间格式

只使用 UTC 接收和返回时间. 使用 ISO8601 格式来生成时间,例如:

"finished_at": "2012-01-01T12:00:00Z"

使用一致的路径格式

资源名称

使用资源名称的复数形式,除非系统中相关的资源是唯一的(例如,在大多数系统,用户的账户永远都只能有一个)。这就能在你引用特定的资源时保持一致的方式。

操作

首选端点布局,因为它不需要对单独的资源有任何特殊的操作. 有些情况下是需要特殊操作的,那就把它们放在一个标准的前缀下,以清楚的界定它们:

/resources/:resource/actions/:action

例如.

/runs/{run_id}/actions/stop

小写的路径和属性

使用小写和用虚线符号分隔的路径名,便于同主机名对齐, 例如:

service-api.com/users
service-api.com/app-setups

属性同样也使用小写,但是使用下划线做分隔,那就属性名在 JavaScript 中就可以不用引号了, 例如:

service_class: "first"

内联外键关系

使用一个内联的对象来序列化外键引用,例如:

{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0..."
  },
  ...}

而不是如下例:

{
  "name": "service-production",
  "owner_id": "5d8201b0...",
  ...}

这种方式使得在不必改变响应结构或者引入更多顶级响应域的前提下内联如更多相关资源的信息,例如:

{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0...",
    "name": "Alice",
    "email": "alice@heroku.com"
  },
  ...}

支持为方便起见的非 id 间接引用

在某些情况下对于端用户而言提供一个 ID 标志一个资源可能会方便些。例如,一个用户会需要一个 Heroku 应用名称,但那个应用时用 UUID 标识的。在这些情况下你可能想要同时接受名称和 ID,例如:

$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod

不要只接受名称而排斥ID.

生成结构性的错误

使用一致的,结构化的错误响应. 包括一个依赖于机器的错误 id,一个人类可读的错误消息,以及可选的一个指出有关该错误及如何解决的信息的 url,例如:

HTTP/1.1 429 Too Many Requests

{
  "id":      "rate_limit",
  "message": "Account reached its API rate limit.",
  "url":     "https://docs.service.com/rate-limits"}

为你的错误格式,以及客户端可能会遇到的错误 id 编写文档。

支持使用 Etag 的缓存

在所有的响应中包含一个 ETag 头,以标识返回资源的特定版本. 用户就能够从 If-None-Match 头获取的值中检查出他们的后续请求的是否已经过时。

使用 Request-Id 跟踪请求

在每一个API响应中包含一个 Request-Id 头,填充一个 UUID 值。如果服务器和客户端都记录了这个值的话,它就能在跟踪和调试请求方面起到作用.

使用范围进行分页

对容易产生大量数据的响应进行分页,使用 Content-Range 头来传送分页请求,详细的可以看看 Heroku 平台有关范围的API 中的请求和响应头、状态码、限制,排序和分页浏览的示例。

展示速率限制状态

来自客户端的速率限制请求用以保护服务的健康,并为其它的客户端保持较高的服务质量. 你可以使用一种 令牌桶算法 来量化请求限制。

可以在 RateLimit-Remaining 响应头中返回每个请求的剩余请求令牌数量。

带有版本的接收头

从一开始就要对API进行版本话。使用接收头,以及一个自定义内容类型来同版本进行交互,例如:

Accept: application/vnd.heroku+json; version=3

不去指定一个默认的版本,而不是要求客户端明确指定它们要使用一个特定的版本。

最小化路径内联

在带有内联父/子资源关系的数据模型中,路径可能会内联得很深,例如:

/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}

可以通过在根路径定位资源来限制内联深度,使用内联来指定范围集合.例如,上述情况中一个 dyno 就属于一个属于 org 的 app:

/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}

提供机器可读的 JSON 模式

提供一个机器可读的模式可以精确的指定你的 API。使用 prmd 来管理你的模式,并确保它能被 prmd verify 验证。

提供人类可读的文档

提供客户端开发者可以用来理解你的 API 的人类可读文档。

如果你使用 prmd 创建了一个如上所述的模式,那么你就可以很容易的使用 prmd doc 来为所有的端点生成 Markdown 文档。

除了端点的详细信息之外,还要提供API概述的一些信息:

  • 认证,包括获取和使用认证令牌。
  • API 稳定性和版本,包括如何选择理想的 API 版本。
  • 通用的请求和响应头。
  • 错误的序列化格式。
  • 用不同的语言使用API的客户端的示例。

提供可执行的示例

提供可执行的示例,用户可以直接在终端中敲入命令来查看 API 的调用如何运行,为了尽可能的扩展,这些示例应该要可以照字面意义使用,以最小化用户尝试这些API所需要做的事情,例如:

$ export TOKEN=... # acquire from dashboard
$ curl -is https://$TOKEN@service.com/users

如果你使用 prmd 生成了Markdown文档,你就将可以不费力的获得每一个端点的示例。

稳定性描述

描述API的稳定性或者依据其成熟性和稳定性的各个点,例如:以原型/开发/成品作为标志节点。

查看 Heroku API兼容性策略 为稳定性和变更管理方法提供一种可能。

一旦你的API被定义为是为生产所准备的和坚固的,那当API版本改变的时候,要使得这些API能有向后的兼容性。在创建一个新的API时,如果你需要做向后不兼容的变更,应增加版本号。

SSL需求

无一例外,要SSL去访问API时,无论用不用SSL,都不必找出以及解释其原因,它们就是需要SSL。

良好打印的默认json

用户第一次查看你的api很可能是在使用curl的命令行里。如果API的响应有良好的打印格式,那在命令行里它们会很容易理解。为了给这些开发者提供方便,良好打印格式的JSON如下:

{
  "beta": false,
  "email": "alice@heroku.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "last_login": "2012-01-01T12:00:00Z",
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T12:00:00Z"}

而不是:

{"beta":false,"email":"alice@heroku.com",
"id":"01234567-89ab-cdef-0123-456789abcdef",
"last_login":"2012-01-01T12:00:00Z",
"created_at":"2012-01-01T12:00:00Z",
"updated_at":"2012-01-01T12:00:00Z"}

要确保在 JSON 结尾有换行,以防止阻塞用户的终端界面。

对于大部分 API 的响应,性能考滤要优先于良好打印。在某些结点(例如高流量结点)或为某些特定用户(例如无 GUI 界面的程序)使用时,你可能会考滤使用高性能而非良好打印的API。

注:headless program 译为 无显示界面的程序,参考自这篇文章

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文