HTTP API 设计指南(转)

英文原文: HTTP API Design Guide
本文译者: LeoXuGarfielt无若--zxp

介绍

本指南描述了一套有关 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译为“无显示界面的程序”,参考自这篇文章.

HTTP API 设计指南(转)

上一篇:C#实现Web文件上传的两种方法


下一篇:C#串口通信—传输文件测试