[译] GitHub API v3 概述

3,850 阅读15分钟
原文链接: zhuanlan.zhihu.com
译者序:GitHub API v3 是典型的 REST 风格的 web API,是学习 REST API 设计的优质样本。原文见 GitHub API v3 | GitHub Developer Guide

Schema 模式

所有的 API 访问都是通过 HTTPS,从 https://api.github.com 访问。所有的数据以 JSON 的形式发送和接收。

空字段以 null 的形式包含,而不是被忽略。

所有的时间戳使用 ISO 8601 格式返回:

YYYY-MM-DDTHH:MM:SSZ

Summary representations 概要描述

当你取回一个资源的列表时,响应包含这个资源的属性的一个子集。这就是这个资源的“概要”描述。

在使用 API 提供某些属性时,需要大量的计算。鉴于性能上的原因,概要描述中剔除了这些属性。想要获取这些属性,需要获取“详细”描述。

示例:当你获取仓库的列表时,你获取到每一个仓库的概要描述。这里我们取回 octokit 组织所拥有的仓库的列表。

GET /orgs/octokit/repos

Detailed representations 详细描述

当你取回一个单独的资源时,响应通常包含资源的所有属性。这就是这个资源的“详细”描述。

注意权限有时候会影响描述中的详情数量。

示例:当你获取一个单独的仓库时,你会得到这个仓库的详细描述。这里,我们取回仓库 octokit/octokit.rb

GET /repos/octokit/octokit.rb

文档对每一个 API 方法提供了一个示例响应。通过例子说明了这个方法返回的所有属性。

Authentication 权限

有三种通过 GitHub API v3 进行鉴权的方式。在某些地方,需要权限的请求将返回 404 Not Found 而不是 403 Forbidden 。这是为了避免私有仓库意外的泄露给未鉴权的用户。

Basic authentication

curl -u "username" https://api.github.com

OAuth2 token (sent in a header)

curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com

OAuth2 token (sent as a parameter)

curl https://api.github.com/?access_token=OAUTH-TOKEN
关于 Oauth2 的更多信息 developer.github.com/apps/buildi…

注意在非网页的应用中,OAuth2 token 可能需要通过编程方式实现。

OAuth2 key/secret

curl 'https://api.github.com/users/whatever?client_id=xxxx&client_secret=yyyy'

这只能在服务器与服务器场景下使用。不要把你的 OAuth 应用的客户端密钥泄露给你的用户。

Failed login limit 失败登录限制

使用无效凭证进行鉴权时会返回 401 Unauthorized

curl -i https://api.github.com -u foo:bar
HTTP/1.1 401 Unauthorized
{
  "message": "Bad credentials",
  "documentation_url": "https://developer.github.com/v3"
}

在检测到短时间内多个使用无效凭证的鉴权操作时,API 将暂时通过 403 Forbidden 拒绝这个用户的鉴权尝试。

curl -i https://api.github.com -u valid_username:valid_password
HTTP/1.1 403 Forbidden
{
  "message": "Maximum number of login attempts exceeded. Please try again later.",
  "documentation_url": "https://developer.github.com/v3"
}

Parameters 参数

很多 API 方法接受可选的参数。对于 GET 请求,任何没有被指定为路径片段的参数可以通过一个 HTTP 查询字符串参数传递。

curl -i "https://api.github.com/repos/vmg/redcarpet/issues?state=closed"

在这个例子中,vmgredcarpet 两个值通过路径中的 :owner:repo 参数提供,而 :state 通过查询字符串。

对于 POST, PATCH, PUT 和 DELETE 请求,未包含在 URL 中的参数需要以 JSON 的形式编码并带有 'application/json' 的 Content-Type:

curl -i -u username -d '{"scopes":["public_repo"]}' https://api.github.com/authorizations

root endpoint 根端口

你可以发一个 GET 请求到这个根端口来获取 REST API v3 所支持的所有端口分类:

curl https://api.github.com

Client errors 客户端错误

在接受请求体的 API 调用中有三种可能出现的客户端错误类型:

  1. 发送了无效的 JSON 将得到一个 400 Bad Request 响应

HTTP/1.1 400 Bad Request Content-Length: 35 {"message":"Problems parsing JSON"}

  1. 发送了错误类型的 JSON 值将得到一个 400 Bad Request 响应

HTTP/1.1 400 Bad Request Content-Length: 40 {"message":"Body should be a JSON object"}

  1. 发送了无效的字段将得到一个 422 Unprocessable Entity 响应

HTTP/1.1 422 Unprocessable Entity Content-Length: 149 { "message": "Validation Failed", "errors": [ { "resource": "Issue", "field": "title", "code": "missing_field" } ] }

所有的错误对象拥有 resource 和 field 属性,因此你的客户端可以分辨问题时什么。

此外还有错误码可以让你指导这个字段出现了什么错误。这些事可能出现的校验错误码:

错误名描述missing表示一个资源不存在missing_field表示资源的一个必需的字段没有被设置invalid表示一个字段的格式是无效的。这个资源的文档应该可以给你更多具体的信息。already_exists表示另一个资源的这个字段使用了同样的值。这可能发生在那些拥有一些唯一键(比如 Label 名)的资源中。

资源也可能会发送一些自定义的验证错误(codecustom)。自定义错误总包含一个 message 字段来描述这个错误,大多数错误还包含了一个 documentation_url 字段,指向一些可能会帮助你解决这个错误的内容。

HTTP redirects HTTP 重定向

API v3 在合适的地方使用了 HTTP 重定向。客户端应该假定任何一个请求都可能导致重定向。收到一个 HTTP 重定向不是一个错误,客户端应该遵循这个重定向。重定向响应有一个 Location 头部字段,包含了客户端应该重新请求的资源的 URI。

状态码描述301永久重定向,你用于请求的 URI 已经被 Location 头部字段指定的 URI 替换。本次和将来所有对这个资源的请求都应该指向这个新的 URI302, 307临时重定向,请求需要按照原样重新发送给 Location 头部字段置顶的 URI但客户端应当在将来的请求中继续使用原URI

其他的重定向状态码的使用与 HTTP 1.1 规范一致。

HTTP verbs HTTP 动词

在可能的情况下,API v3 力求为每一个操作使用合适的 HTTP 动词。

动词描述HEAD可以对任何资源发送来只获取头部信息GET用来检索资源POST用来创建资源PATCH用来使用部分 JSON 数据来更新资源。例如,一个 Issue 资源有 titlecontent 属。一个 PATCH 请求可以接受一个或多个属性来更新资源。PATCH 是一个相对较新且不通用的 HTTP 动词,所以资源端口也接受 POST 请求PUT用来替换资源或集合。对于没有 body 属性的 PUT 请求,确保将 Content-Length 头部设置为零DELETE用来删除资源

Hypermedia 超媒体链接

所有的资源可能会有一个或多个 *_url 属性,链接到其他资源。这是为了提供显示的 URLs 从而正确的 API 客户端不需要自己构造 URLs。十分推荐 API 客户端使用这些 URL。这样做将会使开发者以后升级 API 更简单。所有的 URLs 符合 RFC 6570 URI 模板。

你可以使用类似 uri_template 的东西扩展这些模板:

>> tmpl = URITemplate.new('/notifications{?since,all,participating}')
>> tmpl.expand
=> "/notifications"

>> tmpl.expand :all => 1
=> "/notifications?all=1"

>> tmpl.expand :all => 1, :participating => 1
=> "/notifications?all=1&participating=1"

Pagination 分页

返回多个项目的请求将默认被分页为 30 个项目。你可以使用 ?page 参数进一步指定页面。对于某些资源,你还可以使用 ?per_page 参数设置最多 100 的自定义页面尺寸。注意由于技术上的原因不是所有的端口都遵循 ?per_page 参数,查看 示例

curl 'https://api.github.com/user/repos?page=2&per_page=100'

注意页面计数从 1 开始,忽略 ?page 参数将返回第一页。

关于分页的更多信息 Traversing with Pagination

Link Header 链接头部

注意:使用形式调用而不是构建你自己的 URLs 是很重要的。

链接标题包含了分页信息:

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",<https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

Link 返回头部包含一个或多个超链接引用,其中一部分可以作为 URI 模板扩展。

可能出现的 rel 值有:

名描述next指向结果集的下一页的链接引用last指向结果集的最后一页的链接引用first指向结果集的第一页的链接引用prev指向结果集的上一页的链接引用

Rate Limiting 频率限制

对于使用 Basic Authentication 或者 OAuth 的 API 请求,每个小时内最多发起 5000 次请求。已鉴权的请求被关联到已认证的用户,不论是使用 Basic Authentication 还是 OAuth token 。这意味着同一个用户使用不同的 tokens 认证的所有 OAuth 应用共享每小时 5000 次请求的配额。

对于无鉴权的请求,频率限制允许每个小时最多 60 次请求。无鉴权请求被关联到来源 IP 地址而不是发起请求的用户。

注意搜索 API 拥有其自定义的频率限制规则

每一个请求的响应头部展示了你当前的频率限制状态:

curl -i https://api.github.com/users/octocat
HTTP/1.1 200 OK
Date: Mon, 01 Jul 2013 17:27:06 GMT
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 56
X-RateLimit-Reset: 1372700873

头部名描述X-RateLimit-Limit每小时允许请求的最大数X-RateLimit-Remaining当前频率限制窗口剩余请求数X-RateLimit-Reset使用 UTC epoch seconds 表示的当前频率限制窗口的重置时间

如果你需要不同格式的时间,任何现代编程语言都可以解决。例如,在浏览器打开控制台,你可以以 Javascript Date 对象的形式获取到重置时间。

new Date(1372700873 * 1000)
// => Mon Jul 01 2013 13:47:53 GMT-0400 (EDT)

如果你超出了频率限制,将返回一个错误的响应:

HTTP/1.1 403 Forbidden
Date: Tue, 20 Aug 2013 14:50:41 GMT
Status: 403 Forbidden
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1377013266
{
   "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
   "documentation_url": "https://developer.github.com/v3/#rate-limiting"
}

你可以在不命中 API 的情况下检查你的频率限制状态

Increasing the unauthenticated rate limit for OAuth applications 提高 OAuth 应用的未授权频率限制

如果你的 OAuth 应用需要更高的未授权调用频率限制,可以将你的应用的客户端 ID 和密钥作为查询字符串传递。

curl -i 'https://api.github.com/users/whatever?client_id=xxxx&client_secret=yyyy'
HTTP/1.1 200 OK
Date: Mon, 01 Jul 2013 17:27:06 GMT
Status: 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4966
X-RateLimit-Reset: 1372700873
注意:永远不要把你的客户端密钥分享给其他人或者包含在用户侧的浏览器代码中。只在服务器到服务器调用时使用这里展示的方法。

Staying within the rate limit 保持在频率限制范围内

如果你在使用 Basic Authentication 或 OAuth 时超出了频率限制,你可以通过缓存 API 响应来修复这个问题并使用 条件请求

Abuse rate limits 滥用频率限制

为了在 GitHub 上提供优质的服务,在使用 API 时某些动作可能应用了额外的频率。例如,使用 API 快速的创建内容,暴力轮询而不是使用 webhooks,发起多个不正确的请求,或者重复地请求需要大量计算的数据可能会导致滥用频率限制。

滥用频率限制不会干扰 API 的合理使用。你的常规频率限制是你唯一需要关注的。为了确保你成为一个好的 API 公民,了解一些我们的最佳实践指导

如果你的应用触发了这个频率限制,你讲收到一个提供有用信息的响应:

HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
Connection: close
{
  "message": "You have triggered an abuse detection mechanism and have been temporarily blocked from content creation. Please retry your request again later.",
  "documentation_url": "https://developer.github.com/v3/#abuse-rate-limits"
}

User agent required 必要的用户代理

所有的 API 请求必需包含一个有效的 User-Agent 头部。没有 User-Agent 头部的请求将被拒绝。我们要求你在 User-Agent 头部值使用你的 GitHub 用户名,或者你的应用的名称。这能让我们在有问题时联系到你。

这是示例:

User-Agent: Awesome-Octocat-App

cURL 默认发送一个有效的 User-Agent 头部 。如果你通过 cURL(或通过其他客户端)提供了一个无效的 User-Agent 头部,你将受到一个 403 Forbidden 响应:

curl -iH 'User-Agent: ' https://api.github.com/meta
HTTP/1.0 403 Forbidden
Connection: close
Content-Type: text/html
Request forbidden by administrative rules.
Please make sure your request has a User-Agent header.
Check https://developer.github.com for other possible causes.

Conditional requests 条件请求

大多数响应会返回一个 Etag 头部。很多响应还会返回一个 Last-Modified 头部。你可以使用这些头部的值分别作为接下来对这些资源的请求的 If-None-MatchIf-Modified-Since 头部。如果这些资源没有变化,服务器会返回 304 Not Modified

注意:发起一个条件请求并受到一个 304 响应不会计入你的频率限制,所以我们鼓励你尽可能使用它。
curl -i https://api.github.com/user
HTTP/1.1 200 OK
Cache-Control: private, max-age=60
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
Status: 200 OK
Vary: Accept, Authorization, Cookie
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873
curl -i https://api.github.com/user -H 'If-None-Match: "644b5b0155e6404a9cc4bd9d8b1ae730"'
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
Status: 304 Not Modified
Vary: Accept, Authorization, Cookie
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873
curl -i https://api.github.com/user -H "If-Modified-Since: Thu, 05 Jul 2012 15:31:30 GMT"
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
Status: 304 Not Modified
Vary: Accept, Authorization, Cookie
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873

Cross origin resource sharing 跨域资源共享

API 对任何来源的 AJAX 请求支持跨域资源共享(CORS)。你可以阅读 CORS W3C Recommendation,或者来自 HTML 5 Security Guide 的介绍

这是一个来自浏览器的简单请求,命中 http://example.com:

curl -i https://api.github.com -H "Origin: http://example.com"
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval

CORS 预检请求如下:

curl -i https://api.github.com -H "Origin: http://example.com" -X OPTIONS
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-GitHub-OTP, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Max-Age: 86400

JSON-P callbacks JSON-P 回调函数

你可以向任何一个 GET 调用发送一个 ?callback 参数来让结果包裹在一个 JSON 函数里。这通常在浏览器希望绕过跨域问题在 web 页面内嵌入 GitHub 内容时使用。响应包含和普通 API 相同的输出,以及相关的 HTTP 头部信息。

curl https://api.github.com?callback=foo
/**/foo({
  "meta": {
    "status": 200,
    "X-RateLimit-Limit": "5000",
    "X-RateLimit-Remaining": "4966",
    "X-RateLimit-Reset": "1372700873",
    "Link": [ // pagination headers and other links
      ["https://api.github.com?page=2", {"rel": "next"}]
    ]
  },
  "data": {
    // the data
  }
})

你可以写一个 Javascript 处理函数来处理这个回调。下面是一个你可以尝试的最小示例:

<html>
<head>
<script type="text/javascript">
function foo(response) {
  var meta = response.meta;
  var data = response.data;
  console.log(meta);
  console.log(data);
}

var script = document.createElement('script');
script.src = 'https://api.github.com?callback=foo';

document.getElementsByTagName('head')[0].appendChild(script);
</script>
</head>

<body>
  <p>Open up your browser's console.</p>
</body>
</html>

所有的头部(meta中的字段)是和 HTTP 的头部相同的字符串,有一个值得注意的例外:Link。Link 头部已经为你预解析为一个 [url, options] 元组的数组。

一个如下的 Link :

Link: <url1>; rel="next", <url2>; rel="foo"; bar="baz"

在回调输出中变为:

{
  "Link": [
    [
      "url1",
      {
        "rel": "next"
      }
    ],
    [
      "url2",
      {
        "rel": "foo",
        "bar": "baz"
      }
    ]
  ]
}

Timezones 时区

一些请求允许指定带时区信息的时间戳或生成带时区信息的时间戳。我使用了下列规则,按照优先级次序,来确定 API 调用的时区信息。

显式提供带有时区信息的 ISO 8601 时间戳

对于允许指定时间戳的 API 调用,我们使用精确时间戳。一个例子是 Commits API

这些时间戳看起来像是 2014-02-27T15:05:06+01:00 。也可以看这个例子了解这些时间戳是如何指定的。

使用 Time-Zone 头部

可以提供一个 Time-Zone 头部,根据来自 Olson 数据库的名称列表来定义一个时区

curl -H "Time-Zone: Europe/Amsterdam" -X POST https://api.github.com/repos/github/linguist/contents/new_file.md

这意味着我们生成一个你的 API 调用产生时刻,这个头部定义的时区的时间戳。例如,内容 API 为每一个添加或修改生成一个 git commit 并且使用当前时间作为时间戳。这个头部将确定用来生成当前时间戳的时区。

使用此用户最后已知的时区

如果没有指定 Time-Zone 头部并且你产生了一个已鉴权请求,我们为这个已认证用户使用最后已知的时区。最后已知时区在你每次浏览 GitHub 网站的时候更新。

UTC

如果从上面的步骤都无法得到任何信息,我们使用 UTC 作为时区来创建 git commit。