这篇文章受了很多 Github 接口文档 以及文中所有提及的协议、标准和文章的启发,因此在顶部注明并加以感谢。
HOST 地址:
http://api.example.com
所有 URI 都需要遵循 RFC3986 的要求。
接口遵循“输入宽容,输出严格”原则,输出的数据结构中空字段的值一律为 null
RFC 5646 规定的语言的标签的格式如下:
language-script-region-variant-extension-privateuse
- language:这部分是 ISO 639(Wikipedia) 规定的代码,比如中文是 zh。
- script:表示变体,比如简体汉字是 zh-Hans ,繁体汉字是 zh-Hant 。
- region:是 ISO 3166-1(Wikipedia) 规定的地理区域,比如 zh-Hans-CN 就是中国大陆使用的简体中文。
- variant:表示方言。
- extension:表示扩展。
- privateus:表示私有标识。
有一点需要注意,任何合法的标签都必须经过IANA的认证,已通过认证的标签可以在这个网页查到。此外,网上还有一个非官方的标签搜索引擎。
相关资料:
- Android 文档:http://developer.android.com/guide/topics/resources/providing-resources.html#LocaleQualifier ,顺便鄙视 iOS 文档在获取语言接口的相关文档里根本不提这个。
- 《语种名称代码》:http://www.ruanyifeng.com/blog/2008/02/codes_for_language_names.html
- 《Language tags in HTML and XML》:http://www.w3.org/International/articles/language-tags/
客户端请求服务器时,如果对时间有特殊要求(如某段时间每天的统计信息),则可以参考 IETF 相关草案 增加请求头 Timezone: Asia/Shanghai 。
时区的名称可以参考 tz datebase 。
如果客户端请求时没有指定相应的时区,则服务端默认使用 UTC 时间返回相应数据。
PS 考虑到存在夏时制这种东西,所以不推荐客户端在请求时使用 Offset 。
时间格式遵循 ISO 8601(Wikipedia) 建议的格式:
- 日期
2014-07-09 - 时间
14:31:22+0800 - 具体时间
2007-11-06T16:34:41Z - 持续时间
P1Y3M5DT6H7M30S(表示在一年三个月五天六小时七分三十秒内) - 时间区间
2007-03-01T13:00:00Z/2008-05-11T15:30:00Z、2007-03-01T13:00:00Z/P1Y2M10DT2H30M、P1Y2M10DT2H30M/2008-05-11T15:30:00Z - 重复时间
R3/2004-05-06T13:00:00+08/P0Y6M5DT3H0M0S(表示从2004年5月6日北京时间下午1点起,在半年零5天3小时内,重复3次)
相关资料:
货币名称可以参考 ISO 4217(Wikipedia) 中的约定,标准为货币名称规定了三个字母的货币代码,其中的前两个字母是 ISO 3166-1(Wikipedia) 中定义的双字母国家代码,第三个字母通常是货币的首字母。在货币上使用这些代码消除了货币名称(比如 dollar )或符号(比如 $ )的歧义。
相关资料:
- 《RESTful Web Services Cookbook 中文版》 3.9 节《如何在表述中使用可移植的数据格式》
- 维基百科
- RFC2616
- 如果请求头中存在
X-HTTP-Method-Override或参数中存在_method(拥有更高权重),且值为GET,POST,PUT,DELETE,PATCH,OPTION,HEAD之一,则视作相应的请求方式进行处理 GET,DELETE,HEAD方法,参数风格为标准的GET风格的参数,如url?a=1&b=2POST,PUT,PATCH,OPTION方法- 默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的
Content-Type为application/json - 在一些特殊接口中(会在文档中说明),可能允许
Content-Type为application/x-www-form-urlencoded或者multipart/form-data,此时请求实体会被视作标准POST风格的参数进行处理
- 默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的
关于方法语义的说明:
OPTIONS用于获取资源支持的所有 HTTP 方法HEAD用于只获取请求某个资源返回的头信息GET用于从服务器获取某个资源的信息- 完成请求后返回状态码
200 OK - 完成请求后需要返回被请求的资源详细信息
- 完成请求后返回状态码
POST用于创建新资源- 创建完成后返回状态码
201 Created - 完成请求后需要返回被创建的资源详细信息
- 创建完成后返回状态码
PUT用于完整的替换资源或者创建指定身份的资源,比如创建 id 为 123 的某个资源- 如果是创建了资源,则返回
201 Created - 如果是替换了资源,则返回
200 OK - 完成请求后需要返回被修改的资源详细信息
- 如果是创建了资源,则返回
PATCH用于局部更新资源- 完成请求后返回状态码
200 OK - 完成请求后需要返回被修改的资源详细信息
- 完成请求后返回状态码
DELETE用于删除某个资源- 完成请求后返回状态码
204 No Content
- 完成请求后返回状态码
- 维基百科上的《 HTTP 状态码》词条
- RFC2616 - HTTP 协议本体
- RFC4918 - 422 状态码的来源
- RFC5789 - PATCH 方法的定义
- RFC6585 - 新增的四个 HTTP 状态码,中文版
- Do I need to use http redirect code 302 or 307? - Stack Overflow
- 400 vs 422 response to POST of data
- 200 OK : 请求执行成功并返回相应数据,如
GET成功 - 201 Created : 对象创建成功并返回相应资源数据,如
POST成功;创建完成后响应头中应该携带头标Location,指向新建资源的地址 - 204 No Content : 请求执行成功,不返回相应资源数据,如
PATCH,DELETE成功
重定向的新地址都需要在响应的 Location 头标中返回
- 301 Moved Permanently : 被请求的资源已永久移动到新位置
- 302 Found : 请求的资源现在临时从不同的 URI 响应请求
- 303 See Other : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该使用
GET方法进行请求 - 307 Temporary Redirect : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该保持原有的请求方法进行请求
- 400 Bad Request : 请求体包含语法错误
- 401 Unauthorized : 需要验证用户身份
- 403 Forbidden : 服务器拒绝执行
- 404 Not Found : 找不到目标资源
- 405 Method Not Allowed : 不允许执行目标行为
- 409 Conflict : 被请求的资源的当前状态之间存在冲突
- 410 Gone : 被请求的资源已被删除
- 413 Request Entity Too Large : 请求实体过大
- 415 Unsupported Media Type : 当前请求的方法和所请求的资源不支持请求中提交的实体的格式
- 422 Unprocessable Entity : 请求格式正确,但是由于含有语义错误,无法响应
- 428 Precondition Required : 要求先决条件,如果想要请求能成功必须满足一些预设的条件
- 500 Internal Server Error : 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
- 501 Not Implemented : 服务器不支持当前请求所需要的某个功能。
- 502 Bad Gateway : 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
- 503 Service Unavailable : 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个
Retry-After头用以标明这个延迟时间(内容可以为数字,单位为秒;或者是一个 HTTP 协议指定的时间格式)。如果没有给出这个Retry-After信息,那么客户端应当以处理 500 响应的方式处理它。
501 与 405 的区别是:405 是表示服务端不允许客户端这么做,501 是表示客户端或许可以这么做,但服务端还没有实现这个功能
在调用接口的过程中,可能出现下列几种错误情况:
-
服务器维护中,
503状态码HTTP/1.1 503 Service Unavailable Retry-After: 3600 Content-Length: 41 {"message": "Service In the maintenance"}
-
发送了无法转化的请求体,
400状态码HTTP/1.1 400 Bad Request Content-Length: 35 {"message": "Problems parsing JSON"}
-
服务到期(比如付费的增值服务等),
403状态码HTTP/1.1 403 Forbidden Content-Length: 29 {"message": "Service expired"}
-
因为某些原因不允许访问(比如被 ban ),
403状态码HTTP/1.1 403 Forbidden Content-Length: 29 {"message": "Account blocked"}
-
权限不够,
403状态码HTTP/1.1 403 Forbidden Content-Length: 31 {"message": "Permission denied"}
-
需要修改的资源不存在,
404状态码HTTP/1.1 404 Not Found Content-Length: 32 {"message": "Resource not found"}
-
缺少了必要的头信息,
428状态码HTTP/1.1 428 Precondition Required Content-Length: 35 {"message": "Header User-Agent is required"}
-
发送了非法的资源,
422状态码HTTP/1.1 422 Unprocessable Entity Content-Length: 149 { "message": "Validation Failed", "errors": [ { "resource": "Issue", "field": "title", "code": "missing_field" } ] }
所有的 error 哈希表都有 resource, field, code 字段,以便于定位错误,code 字段则用于表示错误类型:
missing: 说明某个字段的值代表的资源不存在invalid: 某个字段的值非法,接口文档中会提供相应的信息missing_field: 缺失某个必须的字段already_exist: 发送的资源中的某个字段的值和服务器中已有的某个资源冲突,常见于某些值全局唯一的字段,比如 @ 用的用户名(这个错误我有纠结,因为其实有 409 状态码可以表示,但是在修改某个资源时,很一般显然请求中不止是一种错误,如果是 409 的话,多种错误的场景就不合适了)
部分接口需要通过某种身份验证方式才能请求成功(这些接口应该在文档中标注出来),身份验证支持两种方式:
- 支持 HTTP 基本认证
- 支持通过登录接口使用账号密码获取 JSON Web Token ,在请求接口时使用
Authorization: Bearer #{token}头标或者token参数的值的方式进行验证。
REST 服务的要求之一,客户端不再需要将某些接口的 URI 硬编码在代码中,唯一需要存储的只是 API 的 HOST 地址,能够非常有效的降低客户端与服务端之间的耦合,服务端对 URI 的任何改动都不会影响到客户端的稳定。
目前只有两种方案差强人意:
- JSON HAL 草案 ,示例可以参考 JSON HAL 作者自己的介绍
- GitHub API 使用的方案 ,应该是一种 JSON HAL 的变体
可以挑一个来借鉴和实现
请求某个资源集合时,可以通过指定 count 参数来指定每页的资源数量,通过 page 参数指定页码。
如果没有传递 count 参数或者 count 参数的值为空,则使用默认值 20 , count 参数的最大上限为 100 。
分页的相关信息会包含在 Link Header 和 X-Resource-Count 中。
如果是第一页或者是最后一页时,不会返回 prev 和 next 的 Link 。
更多 rel 相关信息,可以参阅 RFC5988 6.2.2节 。
HTTP/1.1 200 OK
X-Resource-Count: 542
Link: <http://api.example.com/#{RESOURCE_URI}?cursor=&count=100>; rel="first",
<http://api.example.com/#{RESOURCE_URI}?cursor=90&count=100>; rel="prev",
<http://api.example.com/#{RESOURCE_URI}?cursor=120&count=100>; rel="next",
<http://api.example.com/#{RESOURCE_URI}?cursor=200&count=100>; rel="last"
[
...
]大部分接口都会在响应头中携带 Last-Modified 和 ETag 信息,你可以在随后请求这些资源的时候,在请求头中使用 If-Modified-Since 或者 If-None-Match 两个头来确认资源是否经过修改。
$ curl -i http://api.example.com/#{RESOURCE_URI}
HTTP/1.1 200 OK
Cache-Control: private, max-age=60
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
$ curl -i http://api.example.com/#{RESOURCE_URI} -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
$ curl -i http://api.example.com/#{RESOURCE_URI} -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请求头中的 User-Agent 头标是必须的,如果没有,则服务器会响应 400 状态码。
建议格式:
-
iOS
iOS/iOS版本号 (设备型号; 是否越狱<unjailbroken, jailbroken>; 网络类型<Wi-Fi, Cellular, Unknown>; 语言) CFBundleIdentifier/CFBundleVersion -
Android
Android/Android版本号 (设备型号; ROM版本号; 是否root<unrooted, rooted>; 网络类型; 语言) PackageName/版本号 -
Web 应用的 User-Agent 由浏览器设定
示例:
User-Agent: iOS/6.1.2 (iPhone 5; jailbroken; Wi-Fi; zh-CN) com.bundle.id/3.2
User-Agent: Android/4.2 (MI-ONE Plus; MIUI-2.3.6f; unrooted; GPRS; zh-TW) com.bundle.id/2.1
Android 的网络类型获取可以参考文档:http://developer.android.com/reference/android/telephony/TelephonyManager.html
接口支持“跨域资源共享”(Cross Origin Resource Sharing, CORS),这里和这里和这份中文资料有一些指导性的资料。
简单示例:
$ curl -i https://api.example.com -H "Origin: http://example.com"
HTTP/1.1 302 Found$ curl -i https://api.example.com -H "Origin: http://example.com"
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, X-Resource-Count
Access-Control-Allow-Credentials: true预检请求的响应示例:
$ curl -i https://api.example.com -H "Origin: http://example.com" -X OPTIONS
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE
Access-Control-Expose-Headers: ETag, Link, X-Resource-Count
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true如果在任何 GET 请求中带有参数 callback ,且值为非空字符串,那么接口将返回如下格式的数据
$ curl http://api.example.com/#{RESOURCE_URI}?callback=foofoo({
"meta": {
"status": 200,
"X-Resource-Count": 542,
"Link": [
{"href": "http://api.example.com/#{RESOURCE_URI}?cursor=0&count=100", "rel": "first"},
{"href": "http://api.example.com/#{RESOURCE_URI}?cursor=90&count=100", "rel": "prev"},
{"href": "http://api.example.com/#{RESOURCE_URI}?cursor=120&count=100", "rel": "next"},
{"href": "http://api.example.com/#{RESOURCE_URI}?cursor=200&count=100", "rel": "last"}
]
},
"data": // data
})推荐参考文档 HTTP API Design Guide 来设计 REST 风格的 API ,我基本同意这个文档上的所有建议,除了以下两点:
- Use consistent path formats
还是不建议将动作写在 URL 中,像文档中的情况,可以将这个行为抽象成一个事务资源
POST /runs/:run_id/stop-logs或者POST /runs/:run_id/stoppers来解决 - Paginate with Ranges
确实是一个巧妙的设计,但似乎并不符合
Content-Range的设计意图,而且有可能和需要使用到Content-Range的正常场景冲突(虽然几乎不可能),所以不推荐