├── .gitignore ├── LICENSE.md ├── SUPPLEMENT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[a-z] 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | Copyright the [project contributors](https://github.com/bolasblack/http-api-guide/graphs/contributors). 4 | 5 | Released under a [MIT License](http://opensource.org/licenses/MIT). 6 | -------------------------------------------------------------------------------- /SUPPLEMENT.md: -------------------------------------------------------------------------------- 1 | # 补充内容 2 | 3 | 这里是一些补充性质的内容,在需要实现某些功能时可以做一个参考,不一定有相应的标准可以遵循。 4 | 5 | ## 目录 6 | 7 | * [扩充巴科斯范式](#user-content-扩充巴科斯范式-abnf) 8 | * [User-Agent](#user-content-user-agent) 9 | * [WWW-Authenticate 头](#user-content-www-authenticate-头) 10 | * [两步验证](#user-content-两步验证) 11 | * [同时操作多个资源](#user-content-同时操作多个资源) 12 | * [超文本驱动](#user-content-超文本驱动) 13 | * [错误处理](#user-content-错误处理) 14 | * [分页](#user-content-分页) 15 | 16 | ## 扩充巴科斯范式 (ABNF) 17 | 18 | 这算是阅读规范的预备知识吧,但写在 README 里还是太占空间了,所以写在了这里。 19 | 20 | 规范里类似: 21 | 22 | ```abnf 23 | header-field = field-name ":" OWS field-value OWS 24 | 25 | field-name = token 26 | field-value = *( field-content / obs-fold ) 27 | field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 28 | field-vchar = VCHAR / obs-text 29 | 30 | obs-fold = CRLF 1*( SP / HTAB ) 31 | ; obsolete line folding 32 | ; see Section 3.2.4 33 | ``` 34 | 35 | 格式的内容叫做“扩充巴科斯范式”,是由 [RFC 5234](http://tools.ietf.org/html/rfc5234) ([Wikipedia](http://zh.wikipedia.org/wiki/%E6%89%A9%E5%85%85%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F)) 定义用以描述一些内容的详细格式的定义语言。 36 | 37 | ## User-Agent 38 | 39 | 请求头中的 `User-Agent` 可以帮助服务端收集设备信息,但格式需要遵循 [RFC 7231](http://tools.ietf.org/html/rfc7231#section-5.5.3) 中的定义,下文是一些建议格式: 40 | 41 | * iOS 42 | 43 | iOS/iOS版本号 (设备型号; 是否越狱; 网络类型; 语言) CFBundleIdentifier/CFBundleVersion 44 | 45 | * Android 46 | 47 | Android/Android版本号 (设备型号; ROM版本号; 是否root; 网络类型; 语言) PackageName/PackageVersion 48 | 49 | * Web 应用的 User-Agent 由浏览器设定 50 | 51 | 示例: 52 | 53 | User-Agent: iOS/6.1.2 (iPhone 5; jailbroken; Wi-Fi; zh-CN) com.bundle.id/3.2 54 | 55 | User-Agent: Android/4.2 (MI-ONE Plus; MIUI-2.3.6f; unrooted; GPRS; zh-TW) com.bundle.id/2.1 56 | 57 | Android 的网络类型获取可以参考文档:[http://developer.android.com/reference/android/telephony/TelephonyManager.html](http://developer.android.com/reference/android/telephony/TelephonyManager.html) 58 | 59 | ## WWW-Authenticate 头 60 | 61 | 如果是自定义的身份验证方式,比如要求请求时带上请求头 `Authentication: Token `,那么一般在 token 验证失败返回 `401` 的 `WWW-Authenticate` 头可以是 `WWW-Authenticate: Token` ,当然也可以带上任意其他自定义信息。客户端在发现自己无法识别的信息时应该略过。 62 | 63 | ## 两步验证 64 | 65 | 如果只是打算简单实现,建议使用 [TOTP](http://tools.ietf.org/html/rfc6238)([Wikipedia](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm)) 协议,可以兼容 [Google Authenticator](https://code.google.com/p/google-authenticator/) 。 66 | 67 | 关于如何在 API 中实现对两步验证的支持,可以参考 [GitHub 的文档](https://developer.github.com/v3/auth/#working-with-two-factor-authentication)。 68 | 69 | 相关资料: 70 | 71 | * 这里是一份 TOTP 协议中密码生成算法的简单说明:[http://jacob.jkrall.net/totp/](http://jacob.jkrall.net/totp/) 72 | * [What are the advantages of TOTP over HOTP?](http://crypto.stackexchange.com/questions/2220/what-are-the-advantages-of-totp-over-hotp) 73 | 74 | ## 同时操作多个资源 75 | 76 | ### 创建多个相同的资源 77 | 78 | 请求: 79 | 80 | ```http 81 | POST /resources HTTP/1.1 82 | 83 | [{ 84 | "id": "1 也允许由客户端直接指定 ID ,比如 UUID", 85 | "name": "resource1", 86 | "property": "a" 87 | }, { 88 | "name": "resource2", 89 | "property": "b" 90 | }, { 91 | "name": "resource3", 92 | "property": "c" 93 | }] 94 | ``` 95 | 96 | 响应: 97 | 98 | ```http 99 | HTTP/1.1 201 Created 100 | Location: /resources/1,2,3 101 | 102 | [{ 103 | "id": "1", 104 | "name": "resource1", 105 | "property": "a" 106 | }, { 107 | "id": "2", 108 | "name": "resource2", 109 | "property": "b" 110 | }, { 111 | "id": "3", 112 | "name": "resource3", 113 | "property": "c" 114 | }] 115 | ``` 116 | 117 | ### 删除多个相同的资源 118 | 119 | ```http 120 | // 请求 121 | DELETE /resources/1,2,3 HTTP/1.1 122 | 123 | // 响应 124 | HTTP/1.1 204 No Content 125 | ``` 126 | 127 | ### 修改多个相同的资源 128 | 129 | 请求: 130 | 131 | ```http 132 | PATCH /resources/1,2,3 HTTP/1.1 133 | 134 | Content-Type: application/json 135 | { 136 | "property": "d" 137 | } 138 | 139 | // 也可以使用 JSON Patch 140 | Content-Type: application/json-patch+json 141 | { 142 | "op": "replace", 143 | "replace": "/property", 144 | "value": "d" 145 | } 146 | ``` 147 | 148 | 请求实体可以直接写一个 JSON 进行修改,也可以发送一个 [JSON Patch](http://tools.ietf.org/html/rfc6902) 进行修改。 149 | 150 | 响应: 151 | 152 | ```http 153 | HTTP/1.1 204 No Content 154 | ``` 155 | 156 | ## 超文本驱动 157 | 158 | 想法受启发于 [JSON API 方案](http://jsonapi.org/),做法基本照搬,主要是把 `links` 相关内容放到了响应头里。 159 | 160 | 可以添加 `schema` 参数链接到目标数据的结构描述文档,比如 [JSON Schema](http://json-schema.org/) 、 [Schema.org](http://schema.org/) 等。 161 | 162 | 想法目前还不成熟,不建议投入使用。 163 | 164 | ```http 165 | HTTP/1.1 200 OK 166 | Link: ; rel="url-template:author"; allow="COLLECTION,GET"; schema="...", 167 | ; rel="url-template:comments"; allow="COLLECTION,CREATE,GET,DELETE"; schema="...", 168 | ; rel="url-template:order"; allow="GET,PUT"; schema="..." 169 | 170 | [{ 171 | "id": "1", 172 | "title": "Rails is Omakase", 173 | "author": "9", 174 | "comments": [ "5", "12", "17", "20" ] 175 | }] 176 | ``` 177 | 178 | ## 错误处理 179 | 180 | 在调用接口的过程中,可能出现下列几种错误情况: 181 | 182 | * 服务器维护中,`503` 状态码 183 | 184 | ```http 185 | HTTP/1.1 503 Service Unavailable 186 | Retry-After: 3600 187 | Content-Length: 41 188 | 189 | {"message": "Service In the maintenance"} 190 | ``` 191 | 192 | * 发送了无法转化的请求体,`400` 状态码 193 | 194 | ```http 195 | HTTP/1.1 400 Bad Request 196 | Content-Length: 35 197 | 198 | {"message": "Problems parsing JSON"} 199 | ``` 200 | 201 | * 服务到期(比如付费的增值服务等), `403` 状态码 202 | 203 | ```http 204 | HTTP/1.1 403 Forbidden 205 | Content-Length: 29 206 | 207 | {"message": "Service expired"} 208 | ``` 209 | 210 | * 因为某些原因不允许访问(比如被 ban ),`403` 状态码 211 | 212 | ```http 213 | HTTP/1.1 403 Forbidden 214 | Content-Length: 29 215 | 216 | {"message": "Account blocked"} 217 | ``` 218 | 219 | * 权限不够,`403` 状态码 220 | 221 | ```http 222 | HTTP/1.1 403 Forbidden 223 | Content-Length: 31 224 | 225 | {"message": "Permission denied"} 226 | ``` 227 | 228 | * 需要修改的资源不存在, `404` 状态码 229 | 230 | ```http 231 | HTTP/1.1 404 Not Found 232 | Content-Length: 32 233 | 234 | {"message": "Resource not found"} 235 | ``` 236 | 237 | * 缺少了必要的头信息,`428` 状态码 238 | 239 | ```http 240 | HTTP/1.1 428 Precondition Required 241 | Content-Length: 35 242 | 243 | {"message": "Header User-Agent is required"} 244 | ``` 245 | 246 | * 发送了非法的资源,`422` 状态码 247 | 248 | ```http 249 | HTTP/1.1 422 Unprocessable Entity 250 | Content-Length: 149 251 | 252 | { 253 | "message": "Validation Failed", 254 | "errors": [ 255 | { 256 | "resource": "Issue", 257 | "field": "title", 258 | "code": "required" 259 | } 260 | ] 261 | } 262 | ``` 263 | 264 | 所有的 `error` 哈希表都有 `resource`, `field`, `code` 字段,以便于定位错误,`code` 字段则用于表示错误类型: 265 | 266 | * `invalid`: 某个字段的值非法,接口文档中会提供相应的信息 267 | * `required`: 缺失某个必须的字段 268 | * `not_exist`: 说明某个字段的值代表的资源不存在 269 | * `already_exist`: 发送的资源中的某个字段的值和服务器中已有的某个资源冲突,常见于某些值全局唯一的字段,比如 @ 用的用户名(这个错误我有纠结,因为其实有 409 状态码可以表示,但是在修改某个资源时,很一般显然请求中不止是一种错误,如果是 409 的话,多种错误的场景就不合适了) 270 | 271 | 其他参考: 272 | 273 | * [Microsoft REST API Guidelines - 7.10.2. Error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses) 274 | * [GitHub Developer - Client errors](https://developer.github.com/v3/#client-errors) 275 | 276 | ## 分页 277 | 278 | 请求某个资源集合时,可以通过指定 `count` 参数来指定每页的资源数量,通过 `page` 参数指定页码,或根据需求使用 `last_cursor` 参数指定上一页最后一个资源的标识符替代 `page` 参数。 279 | 280 | 如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值,建议在设计时设置一个最大值。 281 | 282 | 分页的相关信息可以包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Pagination-Info` 中( HTTP 头的语法格式可以参考 [ABNF List Extension: #rule](https://tools.ietf.org/html/rfc7230#section-7) )。 283 | 284 | 如果是第一页或者是最后一页时,不返回 `previous` 和 `next` 的 Link 。 285 | 286 | ```http 287 | HTTP/1.1 200 OK 288 | X-Pagination-Info: count="542" 289 | Link: ; rel="first", 290 | ; rel="last", 291 | ; rel="previous", 292 | ; rel="next", 293 | ; rel="url-template:pagination" 294 | 295 | [ 296 | ... 297 | ] 298 | ``` 299 | 300 | 相关资料: 301 | 302 | * [RFC 5005 第3节 _Paged Feeds_](http://tools.ietf.org/html/rfc5005#section-3) 303 | * [RFC 5988 6.2.2节 _Initial Registry Contents_](http://tools.ietf.org/html/rfc5988#section-6.2.2) 304 | 305 | 其他参考: 306 | 307 | * [Microsoft REST API Guidelines - 9.8. Pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#98-pagination) 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP 接口设计指北 2 | 3 | 文档主要目的是为大家在设计接口时提供建议,给大家参考 HTTP 或者其他协议/指南已经设计过的内容 4 | 5 | **只是建议,不是必须遵从的要求** 6 | 7 | 大家有什么问题想法或者建议欢迎 [创建 Issue](https://github.com/bolasblack/http-api-guide/issues/new) 或者 [提交 Pull Request](https://github.com/bolasblack/http-api-guide/compare/) 8 | 9 | * [README.md](.) 主要是简单介绍和列出对设计可能会有帮助的资料,少放一些私货 10 | * [SUPPLEMENT.md](./SUPPLEMENT.md) 有一些更细节的接口设计方面的我自己的想法,全是私货 11 | 12 | ## 目录 13 | 14 | * [HTTP 协议](#user-content-http-协议) 15 | * [URL](#user-content-url) 16 | * [空字段](#user-content-空字段) 17 | * [国际化](#user-content-国际化) 18 | * [请求方法](#user-content-请求方法) 19 | * [状态码](#user-content-状态码) 20 | * [身份验证](#user-content-身份验证) 21 | * [超文本驱动和资源发现](#user-content-超文本驱动和资源发现) 22 | * [数据缓存](#user-content-数据缓存) 23 | * [并发控制](#user-content-并发控制) 24 | * [跨域](#user-content-跨域) 25 | * [其他资料](#user-content-其他资料) 26 | * [其他接口设计指南](#user-content-其他接口设计指南) 27 | 28 | ## HTTP 协议 29 | 30 | ### HTTP/1.1 31 | 32 | 2014 年 6 月的时候 IETF 已经正式的废弃了 [RFC 2616](http://tools.ietf.org/html/rfc2616) ,将它拆分为六个单独的协议说明,并重点对原来语义模糊的部分进行了解释: 33 | 34 | * RFC 7230 - HTTP/1.1: [Message Syntax and Routing](http://tools.ietf.org/html/rfc7230) - low-level message parsing and connection management 35 | * RFC 7231 - HTTP/1.1: [Semantics and Content](http://tools.ietf.org/html/rfc7231) - methods, status codes and headers 36 | * RFC 7232 - HTTP/1.1: [Conditional Requests](http://tools.ietf.org/html/rfc7232) - e.g., If-Modified-Since 37 | * RFC 7233 - HTTP/1.1: [Range Requests](http://tools.ietf.org/html/rfc7233) - getting partial content 38 | * RFC 7234 - HTTP/1.1: [Caching](http://tools.ietf.org/html/rfc7234) - browser and intermediary caches 39 | * RFC 7235 - HTTP/1.1: [Authentication](http://tools.ietf.org/html/rfc7235) - a framework for HTTP authentication 40 | 41 | 相关资料: 42 | 43 | * [RFC2616 is Dead](https://www.mnot.net/blog/2014/06/07/rfc2616_is_dead) ([中文版](http://www.infoq.com/cn/news/2014/06/http-11-updated)) 44 | 45 | ### HTTP/2 46 | 47 | HTTP 协议的 2.0 版本还没有正式发布,但目前已经基本稳定下来了。 48 | 49 | [2.0 版本的设计目标是尽量在使用层面上保持与 1.1 版本的兼容,所以,虽然数据交换的格式发生了变化,但语义基本全部被保留下来了](http://http2.github.io/http2-spec/index.html#rfc.section.8)。 50 | 51 | 因此,作为使用者而言,我们并不需要为了支持 2.0 而大幅修改代码。 52 | 53 | * [HTTP/2 latest draft](http://http2.github.io/http2-spec/index.html) 54 | * [HTTP/2 草案的中文版](https://github.com/fex-team/http2-spec/blob/master/HTTP2%E4%B8%AD%E8%8B%B1%E5%AF%B9%E7%85%A7%E7%89%88(06-29).md) 55 | * [HTTP/1.1 和 HTTP/2 数据格式的对比](http://http2.github.io/http2-spec/index.html#rfc.section.8.1.3) 56 | 57 | ## URL 58 | 59 | URL 的设计都需要遵守 [RFC 3986](http://tools.ietf.org/html/rfc3986) 的的规范。 60 | 61 | URL 的长度,在 HTTP/1.1: Message Syntax and Routing([RFC 7230](https://tools.ietf.org/html/rfc7230)) 的 [3.1.1](https://tools.ietf.org/html/rfc7230#section-3.1.1) 小节中有说明,本身不限制长度。但是在实践中,服务器和客户端本身会施加限制*,因此需要根据自己的场景和需求做对应的调整 62 | 63 | * 比如 IE8 的 URL 最大长度是 2083 个字符;nginx 的 [`large_client_header_buffers`](http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers) 默认值是 8k ,整个 [request-line](https://tools.ietf.org/html/rfc7230#section-3.1.1) 超过 8k 时就会返回 414 (Request-URI Too Large) 64 | * [Microsoft REST API Guidelines - 7.2. URL length](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#72-url-length) 65 | 66 | **强烈建议 API 部署 SSL 证书**,这样接口传递的数据的安全性才能获得一定的保障。 67 | 68 | ## 空字段 69 | 70 | 接口遵循“输入宽容,输出严格”原则,输出的数据结构中空字段的值一律为 `null` 71 | 72 | ## 国际化 73 | 74 | ### 语言标签 75 | 76 | [RFC 5646](http://tools.ietf.org/html/rfc5646) ([BCP 47](http://tools.ietf.org/html/bcp47)) 规定的语言标签的格式如下: 77 | 78 | ``` 79 | language-script-region-variant-extension-privateuse 80 | ``` 81 | 82 | 1. `language`:这部分使用的是 ISO 639-1, ISO 639-2, ISO 639-3, ISO 639-5 中定义的语言代码,必填 83 | * 这个部分由 `primary-extlang` 两个部分构成 84 | * `primary` 部分使用 ISO 639-1, ISO 639-2, ISO 639-3, ISO 639-5 中定义的语言代码,优先使用 ISO 639-1 中定义的条目,比如汉语 `zh` 85 | * `extlang` 部分是在某些历史性的兼容性的原因,在需要非常细致地区别 `primary` 语言的时候使用,使用 ISO 639-3 中定义的三个字母的代码,比如普通话 `cmn` 86 | * 虽然 `language` 可以只写 `extlang` 省略 `primary` 部分,但出于兼容性的考虑,还是**建议**加上 `primary` 部分 87 | 2. `script`: 这部分使用的是 [ISO 15924](http://www.unicode.org/iso15924/codelists.html) ([Wikipedia](http://zh.wikipedia.org/wiki/ISO_15924)) 中定义的语言代码,比如简体汉字是 `zh-Hans` ,繁体汉字是 `zh-Hant` 。 88 | 3. `region`: 这部分使用的是 [ISO 3166-1][iso3166-1] ([Wikipedia][iso3166-1_wiki]) 中定义的地理区域代码,比如 `zh-Hans-CN` 就是中国大陆使用的简体中文。 89 | 4. `variant`: 用来表示 `extlang` 的定义里没有包含的方言,具体的使用方法可以参考 [RFC 5646](http://tools.ietf.org/html/rfc5646#section-2.2.5) 。 90 | 5. `extension`: 用来为自己的应用做一些语言上的额外的扩展,具体的使用方法可以参考 [RFC 5646](http://tools.ietf.org/html/rfc5646#section-2.2.6) 。 91 | 6. `privateuse`: 用来表示私有协议中约定的一些语言上的区别,具体的使用方法可以参考 [RFC 5646](http://tools.ietf.org/html/rfc5646#section-2.2.7) 。 92 | 93 | 其中只有 `language` 部分是必须的,其他部分都是可选的;不过为了便于编写程序,建议设计接口时约定语言标签的结构,比如统一使用 `language-script-region` 的形式( `zh-Hans-CN`, `zh-Hant-HK` 等等)。 94 | 95 | 语言标签是大小写不敏感的,但按照惯例,建议 `script` 部分首字母大写, `region` 部分全部大写,其余部分全部小写。 96 | 97 | **有一点需要注意,任何合法的标签都必须经过 IANA 的认证,已通过认证的标签可以在[这个网页](http://www.iana.org/assignments/language-subtag-registry)查到。此外,网上还有一个非官方的[标签搜索引擎](http://people.w3.org/rishida/utils/subtags/)。** 98 | 99 | 相关资料: 100 | 101 | * ISO 639-1 Code List ([Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)) 102 | * [ISO 639-2 Code List](http://www.loc.gov/standards/iso639-2/php/code_list.php) ([Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes)) 103 | * [ISO 639-3 Code List](http://www-01.sil.org/iso639-3/codes.asp?order=639_3&letter=%25) 104 | * [ISO 639-5 Code List](http://www.loc.gov/standards/iso639-5/id.php) ([Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-5_codes)) 105 | * [ISO 639-3 Macrolanguage Mappings](http://www-01.sil.org/iso639-3/macrolanguages.asp) ([Wikipedia](https://en.wikipedia.org/wiki/ISO_639_macrolanguage)) 106 | * [Relationship between ISO 639-3 and the other parts of ISO 639](http://www-01.sil.org/iso639-3/relationship.asp) 107 | * [网页头部的声明应该是用 lang="zh" 还是 lang="zh-cn"? - 知乎](http://www.zhihu.com/question/20797118) 108 | * [IETF language tag - Wikipedia](https://en.wikipedia.org/wiki/IETF_language_tag) 109 | * [语种名称代码](http://www.ruanyifeng.com/blog/2008/02/codes_for_language_names.html) :文中对带有方言( `extlang` )部分的标签介绍有误 110 | * [Language tags in HTML and XML](http://www.w3.org/International/articles/language-tags/) 111 | * [Choosing a Language Tag](http://www.w3.org/International/questions/qa-choosing-language-tags) 112 | 113 | ### 时区 114 | 115 | 客户端请求服务器时,如果对时间有特殊要求(如某段时间每天的统计信息),则可以参考 [IETF 相关草案](http://tools.ietf.org/html/draft-sharhalakis-httptz-05) 增加请求头 `Timezone` 。 116 | 117 | ``` 118 | Timezone: 2016-11-06 23:55:52+08:00;;Asia/Shanghai 119 | ``` 120 | 121 | 具体格式说明: 122 | 123 | ``` 124 | Timezone: RFC3339 约定的时间格式;POSIX 1003.1 约定的时区字符串;tz datebase 里的时区名称 125 | ``` 126 | 127 | 客户端最好提供所有字段,如果没有办法提供,则应该使用空字符串 128 | 129 | 如果客户端请求时没有指定相应的时区,则服务端默认使用最后一次已知时区或者 [UTC](http://zh.wikipedia.org/wiki/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6) 时间返回相应数据。 130 | 131 | PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time)这种东西,所以不推荐客户端在请求时使用 Offset 。 132 | 133 | 相关资料: 134 | 135 | * [RFC3339](https://tools.ietf.org/html/rfc3339) 136 | * [tz datebase](http://www.iana.org/time-zones) ([Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) 137 | * POSIX 1003.1 时区字符串的说明文档 138 | * [GNU 的文档](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html) 139 | * [IBM 的文章](https://www.ibm.com/developerworks/aix/library/au-aix-posix/) 140 | 141 | ### 时间格式 142 | 143 | 时间格式遵循 [ISO 8601](https://www.iso.org/obp/ui/#iso:std:iso:8601:ed-3:v1:en)([Wikipedia](https://en.wikipedia.org/wiki/ISO_8601)) 建议的格式: 144 | 145 | * 日期 `2014-07-09` 146 | * 时间 `14:31:22+0800` 147 | * 具体时间 `2007-11-06T16:34:41Z` 148 | * 持续时间 `P1Y3M5DT6H7M30S` (表示在一年三个月五天六小时七分三十秒内) 149 | * 时间区间 `2007-03-01T13:00:00Z/2008-05-11T15:30:00Z` 、 `2007-03-01T13:00:00Z/P1Y2M10DT2H30M` 、 `P1Y2M10DT2H30M/2008-05-11T15:30:00Z` 150 | * 重复时间 `R3/2004-05-06T13:00:00+08/P0Y6M5DT3H0M0S` (表示从2004年5月6日北京时间下午1点起,在半年零5天3小时内,重复3次) 151 | 152 | 相关资料: 153 | 154 | * [What's the difference between ISO 8601 and RFC 3339 Date Formats?](http://stackoverflow.com/questions/522251/whats-the-difference-between-iso-8601-and-rfc-3339-date-formats) 155 | * [JSON风格指南 - Google 风格指南(中文版)](https://github.com/darcyliu/google-styleguide/blob/master/JSONStyleGuide.md#%E5%B1%9E%E6%80%A7%E5%80%BC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B) 156 | 157 | ### 货币名称 158 | 159 | 货币名称可以参考 [ISO 4217](javascript:;)([Wikipedia](http://en.wikipedia.org/wiki/ISO_4217)) 中的约定,标准为货币名称规定了三个字母的货币代码,其中的前两个字母是 [ISO 3166-1][iso3166-1]([Wikipedia][iso3166-1_wiki]) 中定义的双字母国家代码,第三个字母通常是货币的首字母。在货币上使用这些代码消除了货币名称(比如 dollar )或符号(比如 $ )的歧义。 160 | 161 | 相关资料: 162 | 163 | * 《RESTful Web Services Cookbook 中文版》 3.9 节《如何在表述中使用可移植的数据格式》 164 | 165 | ## 请求方法 166 | 167 | * 如果请求头中存在 `X-HTTP-Method-Override` 或参数中存在 `_method`(拥有更高权重),且值为 `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`, `HEAD` 之一,则视作相应的请求方式进行处理 168 | * `GET`, `DELETE`, `HEAD` 方法,参数风格为标准的 `GET` 风格的参数,如 `url?a=1&b=2` 169 | * `POST`, `PUT`, `PATCH`, `OPTIONS` 方法 170 | * 默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的 `Content-Type` 为 `application/json` 171 | * 在一些特殊接口中(会在文档中说明),可能允许 `Content-Type` 为 `application/x-www-form-urlencoded` 或者 `multipart/form-data` ,此时请求实体会被视作标准 `POST` 风格的参数进行处理 172 | 173 | 关于方法语义的说明: 174 | 175 | * `OPTIONS` 用于获取资源支持的所有 HTTP 方法 176 | * `HEAD` 用于只获取请求某个资源返回的头信息 177 | * `GET` 用于从服务器获取某个资源的信息 178 | * 完成请求后返回状态码 `200 OK` 179 | * 完成请求后需要返回被请求的资源详细信息 180 | * `POST` 用于创建新资源 181 | * 创建完成后返回状态码 `201 Created` 182 | * 完成请求后需要返回被创建的资源详细信息 183 | * `PUT` 用于完整的替换资源或者创建指定身份的资源,比如创建 id 为 123 的某个资源 184 | * 如果是创建了资源,则返回 `201 Created` 185 | * 如果是替换了资源,则返回 `200 OK` 186 | * 完成请求后需要返回被修改的资源详细信息 187 | * `PATCH` 用于局部更新资源 188 | * 完成请求后返回状态码 `200 OK` 189 | * 完成请求后需要返回被修改的资源详细信息 190 | * `DELETE` 用于删除某个资源 191 | * 完成请求后返回状态码 `204 No Content` 192 | 193 | 相关资料: 194 | 195 | * [RFC 7231 中对请求方法的定义](http://tools.ietf.org/html/rfc7231#section-4.3) 196 | * [RFC 5789](http://tools.ietf.org/html/rfc5789) - PATCH 方法的定义 197 | * [维基百科](http://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#.E8.AF.B7.E6.B1.82.E6.96.B9.E6.B3.95) 198 | 199 | ## 状态码 200 | 201 | ### 请求成功 202 | 203 | * 200 **OK** : 请求执行成功并返回相应数据,如 `GET` 成功 204 | * 201 **Created** : 对象创建成功并返回相应资源数据,如 `POST` 成功;创建完成后响应头中应该携带头标 `Location` ,指向新建资源的地址 205 | * 202 **Accepted** : 接受请求,但无法立即完成创建行为,比如其中涉及到一个需要花费若干小时才能完成的任务。返回的实体中应该包含当前状态的信息,以及指向处理状态监视器或状态预测的指针,以便客户端能够获取最新状态。 206 | * 204 **No Content** : 请求执行成功,不返回相应资源数据,如 `PATCH` , `DELETE` 成功 207 | 208 | ### 重定向 209 | 210 | **重定向的新地址都需要在响应头 `Location` 中返回** 211 | 212 | * 301 **Moved Permanently** : 被请求的资源已永久移动到新位置 213 | * 302 **Found** : 请求的资源现在临时从不同的 URI 响应请求 214 | * 303 **See Other** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该使用 `GET` 方法进行请求。比如在创建已经被创建的资源时,可以返回 `303` 215 | * 307 **Temporary Redirect** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该保持原有的请求方法进行请求 216 | 217 | ### 条件请求 218 | 219 | * 304 **Not Modified** : 资源自从上次请求后没有再次发生变化,主要使用场景在于实现[数据缓存](#user-content-数据缓存) 220 | * 409 **Conflict** : 请求操作和资源的当前状态存在冲突。主要使用场景在于实现[并发控制](#user-content-并发控制) 221 | * 412 **Precondition Failed** : 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。主要使用场景在于实现[并发控制](#user-content-并发控制) 222 | 223 | ### 客户端错误 224 | 225 | * 400 **Bad Request** : 请求体包含语法错误 226 | * 401 **Unauthorized** : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 `403 Forbidden` 。如果请求里有 `Authorization` 头,那么必须返回一个 [`WWW-Authenticate`](https://tools.ietf.org/html/rfc7235#section-4.1) 头 227 | * 403 **Forbidden** : 服务器拒绝执行 228 | * 404 **Not Found** : 找不到目标资源 229 | * 405 **Method Not Allowed** : 不允许执行目标方法,响应中应该带有 `Allow` 头,内容为对该资源有效的 HTTP 方法 230 | * 406 **Not Acceptable** : 服务器不支持客户端请求的内容格式,但响应里会包含服务端能够给出的格式的数据,并在 `Content-Type` 中声明格式名称 231 | * 410 **Gone** : 被请求的资源已被删除,只有在确定了这种情况是永久性的时候才可以使用,否则建议使用 `404 Not Found` 232 | * 413 **Payload Too Large** : `POST` 或者 `PUT` 请求的消息实体过大 233 | * 415 **Unsupported Media Type** : 服务器不支持请求中提交的数据的格式 234 | * 422 **Unprocessable Entity** : 请求格式正确,但是由于含有语义错误,无法响应 235 | * 428 **Precondition Required** : 要求先决条件,如果想要请求能成功必须满足一些预设的条件 236 | 237 | ### 服务端错误 238 | 239 | * 500 **Internal Server Error** : 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。 240 | * 501 **Not Implemented** : 服务器不支持当前请求所需要的某个功能。 241 | * 502 **Bad Gateway** : 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。 242 | * 503 **Service Unavailable** : 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个 `Retry-After` 头用以标明这个延迟时间(内容可以为数字,单位为秒;或者是一个 [HTTP 协议指定的时间格式](http://tools.ietf.org/html/rfc2616#section-3.3))。如果没有给出这个 `Retry-After` 信息,那么客户端应当以处理 500 响应的方式处理它。 243 | 244 | `501` 与 `405` 的区别是:`405` 是表示服务端不允许客户端这么做,`501` 是表示客户端或许可以这么做,但服务端还没有实现这个功能 245 | 246 | 相关资料: 247 | 248 | * [RFC 里的状态码列表](http://tools.ietf.org/html/rfc7231#page-49) 249 | * [RFC 4918](http://tools.ietf.org/html/rfc4918) - 422 状态码的定义 250 | * [RFC 6585](http://tools.ietf.org/html/rfc6585) - 新增的四个 HTTP 状态码,[中文版](http://www.oschina.net/news/28660/new-http-status-codes) 251 | * [维基百科上的《 HTTP 状态码》词条](http://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81) 252 | * [Do I need to use http redirect code 302 or 307? - Stack Overflow](http://stackoverflow.com/questions/2467664/do-i-need-to-use-http-redirect-code-302-or-307) 253 | * [400 vs 422 response to POST of data](http://stackoverflow.com/questions/16133923/400-vs-422-response-to-post-of-data) 254 | * [HTTP Status Codes Decision Diagram – Infographic](https://www.loggly.com/blog/http-status-code-diagram/) 255 | * [HTTP Status Codes](https://httpstatuses.com/) 256 | 257 | ## 身份验证 258 | 259 | 部分接口需要通过某种身份验证方式才能请求成功(这些接口**应该**在文档中标注出来),合适的身份验证解决方案目前有两种: 260 | 261 | * [HTTP 基本认证](http://zh.wikipedia.org/wiki/HTTP%E5%9F%BA%E6%9C%AC%E8%AE%A4%E8%AF%81),**最好只在部署了 SSL 证书的情况下才可以使用,否则用户密码会有暴露的风险** 262 | * [OAuth 2.0](https://tools.ietf.org/html/rfc6749) 263 | * [官网](http://oauth.net/2/) 264 | * [理解OAuth 2.0 - 阮一峰](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html) 以及对[文中 `state` 参数的介绍的修正](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html#comment-323002) 265 | * [JSON Web Token](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25) ,一种 Token 的生成标准 266 | * [Json Web Tokens: Introduction](http://angular-tips.com/blog/2014/05/json-web-tokens-introduction/) 267 | * [Json Web Tokens: Examples](http://angular-tips.com/blog/2014/05/json-web-tokens-examples/) 268 | 269 | ## 超文本驱动和资源发现 270 | 271 | REST 服务的要求之一就是[超文本驱动](http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven),客户端不再需要将某些接口的 URI 硬编码在代码中,唯一需要存储的只是 API 的 HOST 地址,能够非常有效的降低客户端与服务端之间的耦合,服务端对 URI 的任何改动都不会影响到客户端的稳定。 272 | 273 | 目前有几种方案试图实现这个效果: 274 | 275 | * [JSON HAL](http://tools.ietf.org/html/draft-kelly-json-hal-07) ,示例可以参考 [JSON HAL 作者自己的介绍](http://stateless.co/hal_specification.html) 276 | * [GitHub API 使用的方案](https://developer.github.com/v3/#hypermedia) ,应该是一种 JSON HAL 的变体 277 | * [JSON API](http://jsonapi.org/) ,(这里有 [@迷渡](https://github.com/justjavac) 发起的 [中文版](http://jsonapi.org.cn/) ),另外一种类似 JSON HAL 的方案 278 | * [Micro API](http://micro-api.org/) ,一种试图与 [JSON-LD](http://json-ld.org/) 兼容的方案 279 | 280 | 目前所知的方案都实现了发现资源的功能,服务端同时需要实现 `OPTIONS` 方法,并在响应中携带 `Allow` 头来告知客户端当前拥有的操作权限。 281 | 282 | ## 数据缓存 283 | 284 | 大部分接口应该在响应头中携带 `Last-Modified`, `ETag`, `Vary`, `Date` 信息,客户端可以在随后请求这些资源的时候,在请求头中使用 `If-Modified-Since`, `If-None-Match` 等请求头来确认资源是否经过修改。 285 | 286 | 如果资源没有进行过修改,那么就可以响应 `304 Not Modified` 并且不在响应实体中返回任何内容。 287 | 288 | ```bash 289 | $ curl -i http://api.example.com/#{RESOURCE_URI} 290 | HTTP/1.1 200 OK 291 | Cache-Control: public, max-age=60 292 | Date: Thu, 05 Jul 2012 15:31:30 GMT 293 | Vary: Accept, Authorization 294 | ETag: "644b5b0155e6404a9cc4bd9d8b1ae730" 295 | Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT 296 | 297 | Content 298 | ``` 299 | 300 | ```bash 301 | $ curl -i http://api.example.com/#{RESOURCE_URI} -H "If-Modified-Since: Thu, 05 Jul 2012 15:31:30 GMT" 302 | HTTP/1.1 304 Not Modified 303 | Cache-Control: public, max-age=60 304 | Date: Thu, 05 Jul 2012 15:31:45 GMT 305 | Vary: Accept, Authorization 306 | Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT 307 | ``` 308 | 309 | ```bash 310 | $ curl -i http://api.example.com/#{RESOURCE_URI} -H 'If-None-Match: "644b5b0155e6404a9cc4bd9d8b1ae730"' 311 | HTTP/1.1 304 Not Modified 312 | Cache-Control: public, max-age=60 313 | Date: Thu, 05 Jul 2012 15:31:55 GMT 314 | Vary: Accept, Authorization 315 | ETag: "644b5b0155e6404a9cc4bd9d8b1ae730" 316 | Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT 317 | ``` 318 | 319 | 相关资料: 320 | 321 | * [RFC 7232](http://tools.ietf.org/html/rfc7232) 322 | * [HTTP 缓存 - Google Developers](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn) 323 | * [RFC 2616 中缓存过期时间的算法](http://tools.ietf.org/html/rfc2616#section-13.2.3), [MDN 版](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ), [中文版](http://blog.csdn.net/woxueliuyun/article/details/41077671) 324 | * [HTTP 协议中 Vary 的一些研究](https://www.imququ.com/post/vary-header-in-http.html) 325 | * [Cache Control 與 ETag](https://blog.othree.net/log/2012/12/22/cache-control-and-etag/) 326 | 327 | ## 并发控制 328 | 329 | 不严谨的实现,或者缺少并发控制的 `PUT` 和 `PATCH` 请求可能导致 “更新丢失”。这个时候可以使用 `Last-Modified` 和/或 `ETag` 头来实现条件请求,支持乐观并发控制。 330 | 331 | 下文只考虑使用 `PUT` 和 `PATCH` 方法更新资源的情况。 332 | 333 | * 客户端发起的请求如果没有包含 `If-Unmodified-Since` 或者 `If-Match` 头,那就返回状态码 `403 Forbidden` ,在响应正文中解释为何返回该状态码 334 | * 客户端发起的请求提供的 `If-Unmodified-Since` 或者 `If-Match` 头与服务器记录的实际修改时间或 `ETag` 值不匹配的时候,返回状态码 `412 Precondition Failed` 335 | * 客户端发起的请求提供的 `If-Unmodified-Since` 或者 `If-Match` 头与服务器记录的实际修改时间或 `ETag` 的历史值匹配,但资源已经被修改过的时候,返回状态码 `409 Conflict` 336 | * 客户端发起的请求提供的条件符合实际值,那就更新资源,响应 `200 OK` 或者 `204 No Content` ,并且包含更新过的 `Last-Modified` 和/或 `ETag` 头,同时包含 `Content-Location` 头,其值为更新后的资源 URI 337 | 338 | 相关资料: 339 | 340 | * 《RESTful Web Services Cookbook 中文版》 10.4 节 《如何在服务器端实现条件 PUT 请求》 341 | * [RFC 7232 "Conditional Requests"](https://tools.ietf.org/html/rfc7232) 342 | * [Location vs. Content-Location](https://www.subbu.org/blog/2008/10/location-vs-content-location) 343 | 344 | ## 跨域 345 | 346 | ### CORS 347 | 348 | 接口支持[“跨域资源共享”(Cross Origin Resource Sharing, CORS)](http://www.w3.org/TR/cors),[这里](http://enable-cors.org/)和[这里](http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity)和[这份中文资料](http://newhtml.net/using-cors/)有一些指导性的资料。 349 | 350 | 简单示例: 351 | 352 | ```bash 353 | $ curl -i https://api.example.com -H "Origin: http://example.com" 354 | HTTP/1.1 302 Found 355 | ``` 356 | 357 | ```bash 358 | $ curl -i https://api.example.com -H "Origin: http://example.com" 359 | HTTP/1.1 302 Found 360 | Access-Control-Allow-Origin: * 361 | Access-Control-Expose-Headers: ETag, Link, X-Total-Count 362 | Access-Control-Allow-Credentials: true 363 | ``` 364 | 365 | 预检请求的响应示例: 366 | 367 | ```bash 368 | $ curl -i https://api.example.com -H "Origin: http://example.com" -X OPTIONS 369 | HTTP/1.1 302 Found 370 | Access-Control-Allow-Origin: * 371 | Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With 372 | Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE 373 | Access-Control-Expose-Headers: ETag, Link, X-Total-Count 374 | Access-Control-Max-Age: 86400 375 | Access-Control-Allow-Credentials: true 376 | ``` 377 | 378 | ### JSON-P 379 | 380 | 如果在任何 `GET` 请求中带有参数 `callback` ,且值为非空字符串,那么接口将返回如下格式的数据 381 | 382 | ```bash 383 | $ curl http://api.example.com/#{RESOURCE_URI}?callback=foo 384 | ``` 385 | 386 | ```javascript 387 | foo({ 388 | "meta": { 389 | "status": 200, 390 | "X-Total-Count": 542, 391 | "Link": [ 392 | {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=0&count=100", "rel": "first"}, 393 | {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=90&count=100", "rel": "prev"}, 394 | {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=120&count=100", "rel": "next"}, 395 | {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=200&count=100", "rel": "last"} 396 | ] 397 | }, 398 | "data": // data 399 | }) 400 | ``` 401 | 402 | ## 其他资料 403 | 404 | * [Httpbis Status Pages](https://tools.ietf.org/wg/httpbis/) 405 | * [所有在 IANA 注册的消息头和相关标准的列表](http://www.iana.org/assignments/message-headers/message-headers.xhtml) 406 | * [Standards.REST](https://standards.rest/) 里面收集了不少对 REST API 设计有借鉴意义的标准和规范 407 | 408 | ## 其他接口设计指南 409 | 410 | 这里还有一些其他参考资料: 411 | 412 | * [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md) ,很多设计都很有意思,比如: 413 | * [7.10.2. Error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses) 414 | * [9.8. Pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#98-pagination) 415 | * [10. Delta queries](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#10-delta-queries) 416 | * [13. Long running operations](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations) 417 | * [GitHub Developer - REST API v3](https://developer.github.com/v3/) 418 | * [HTTP API Design Guide](https://github.com/interagent/http-api-design/) ,有以下两点我个人并不建议参考: 419 | * [Use consistent path formats](https://github.com/interagent/http-api-design/#use-consistent-path-formats) 420 | 还是不建议将动作写在 URL 中,像文档中的情况,可以将这个行为抽象成一个事务资源 `POST /runs/:run_id/stop-logs` 或者 `POST /runs/:run_id/stoppers` 来解决 421 | * [Paginate with Ranges](https://github.com/interagent/http-api-design/#paginate-with-ranges) 422 | 确实是一个巧妙的设计,但似乎并不符合 `Content-Range` 的设计意图,而且有可能和需要使用到 `Content-Range` 的正常场景冲突(虽然几乎不可能),所以不推荐 423 | * [Best Practices for Designing a Pragmatic RESTful API](http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api) 424 | * [Thoughts on RESTful API Design](http://restful-api-design.readthedocs.org/en/latest/) 425 | * [The RESTful CookBook](http://restcookbook.com/) 426 | * [Google API 设计指南](https://docs.cloud.google.com/apis/design) - 这是一份从 2014 年起开始在 Google 内部使用的的 API 设计指南,我没有看完,不过觉得如果问题的时候过去看看找找灵感可能也不错。“本指南是一份活文档,随着时间的推移,我们会采纳和批准新的风格和设计模式,为本指南增加相关内容。本着这种精神,我们会不断完善本指南,并为 API 设计的艺术和技巧提供充足的空间。” 427 | 428 | [iso3166-1]: javascript:; 429 | [iso3166-1_wiki]: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 430 | --------------------------------------------------------------------------------