From ba69ca8867b50d4d01ce6a99a7de191fb17a8261 Mon Sep 17 00:00:00 2001 From: c4605 Date: Sun, 16 Aug 2015 12:29:54 +0800 Subject: [PATCH 01/17] =?UTF-8?q?feat(=E8=B6=85=E6=96=87=E6=9C=AC=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=E5=92=8C=E8=B5=84=E6=BA=90=E5=8F=91=E7=8E=B0):=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=20`Micro=20API`=20=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=B5=84=E6=BA=90=E6=93=8D=E4=BD=9C=E6=9D=83=E9=99=90?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E7=9A=84=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dc0218d..4e2e50e 100644 --- a/README.md +++ b/README.md @@ -344,15 +344,16 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time ## 超文本驱动和资源发现 -REST 服务的要求之一,客户端不再需要将某些接口的 URI 硬编码在代码中,唯一需要存储的只是 API 的 HOST 地址,能够非常有效的降低客户端与服务端之间的耦合,服务端对 URI 的任何改动都不会影响到客户端的稳定。 +REST 服务的要求之一就是[超文本驱动](http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven),客户端不再需要将某些接口的 URI 硬编码在代码中,唯一需要存储的只是 API 的 HOST 地址,能够非常有效的降低客户端与服务端之间的耦合,服务端对 URI 的任何改动都不会影响到客户端的稳定。 -目前只有几种方案差强人意: +目前有几种方案试图实现这个效果: -* [JSON HAL 草案](http://tools.ietf.org/html/draft-kelly-json-hal-06) ,示例可以参考 [JSON HAL 作者自己的介绍](http://stateless.co/hal_specification.html) +* [JSON HAL](http://tools.ietf.org/html/draft-kelly-json-hal-07) ,示例可以参考 [JSON HAL 作者自己的介绍](http://stateless.co/hal_specification.html) * [GitHub API 使用的方案](https://developer.github.com/v3/#hypermedia) ,应该是一种 JSON HAL 的变体 -* [JSON API 方案](http://jsonapi.org/) (这里有 [@迷渡](https://github.com/justjavac) 发起的 [中文版](http://jsonapi.org.cn/) ),另外一种类似 JSON HAL 的方案,不过某些方面(比如甚至也考虑到了 URL )考虑的比 JSON HAL 更为具体 +* [JSON API](http://jsonapi.org/) ,(这里有 [@迷渡](https://github.com/justjavac) 发起的 [中文版](http://jsonapi.org.cn/) ),另外一种类似 JSON HAL 的方案 +* [Micro API](http://micro-api.org/) ,一种试图与 [JSON-LD](http://json-ld.org/) 兼容的方案 -目前来看应该是合并 JSON API 和 JSON HAL 两个方案的做法,各取所长,能够得到一个相对理想的方案 +目前所知的方案都实现了发现资源的功能,服务端同时需要实现 `OPTIONS` 方法,并在响应中携带 `Allow` 头来告知客户端当前拥有的操作权限。 ## 分页 From 3f3e1b92b3ead9d7c9549dc3ae02ea17dcba89ed Mon Sep 17 00:00:00 2001 From: c4605 Date: Sat, 29 Aug 2015 14:19:23 +0800 Subject: [PATCH 02/17] =?UTF-8?q?change(=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86):=20=E4=BD=BF=E7=94=A8=20required=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=20missing=5Ffield=20=EF=BC=8Cnot=5Fexist=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=20missing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 似乎 missing 代表字段对应的资源不存在不是非常合适,理解起来会更偏向于认为缺失某个字段 所以修改一下 missing 和 missing_field 这两个错误码,让人更容易理解 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4e2e50e..50fab0e 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time { "resource": "Issue", "field": "title", - "code": "missing_field" + "code": "required" } ] } @@ -324,9 +324,9 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time 所有的 `error` 哈希表都有 `resource`, `field`, `code` 字段,以便于定位错误,`code` 字段则用于表示错误类型: -* `missing`: 说明某个字段的值代表的资源不存在 * `invalid`: 某个字段的值非法,接口文档中会提供相应的信息 -* `missing_field`: 缺失某个必须的字段 +* `required`: 缺失某个必须的字段 +* `not_exist`: 说明某个字段的值代表的资源不存在 * `already_exist`: 发送的资源中的某个字段的值和服务器中已有的某个资源冲突,常见于某些值全局唯一的字段,比如 @ 用的用户名(这个错误我有纠结,因为其实有 409 状态码可以表示,但是在修改某个资源时,很一般显然请求中不止是一种错误,如果是 409 的话,多种错误的场景就不合适了) ## 身份验证 From 3a9785280595e36f50018b57b11a872892d5c5d9 Mon Sep 17 00:00:00 2001 From: Huan Huang Date: Fri, 15 Apr 2016 18:11:41 +0800 Subject: [PATCH 03/17] =?UTF-8?q?"=E5=A6=82=E4=BD=95"->"=E5=A6=82=E6=9E=9C?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 表述错误 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50fab0e..4c91c15 100644 --- a/README.md +++ b/README.md @@ -361,7 +361,7 @@ REST 服务的要求之一就是[超文本驱动](http://roy.gbiv.com/untangled/ 如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值 20 , `count` 参数的最大上限为 100 。 -如何同时传递了 `last_cursor` 和 `page` 参数,则使用 `page` 。 +如果同时传递了 `last_cursor` 和 `page` 参数,则使用 `page` 。 分页的相关信息会包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Total-Count` 中。 From 8f303e552fc14d70529616ad4df22a81a2edd70a Mon Sep 17 00:00:00 2001 From: c4605 Date: Sat, 5 Nov 2016 20:37:24 +0800 Subject: [PATCH 04/17] =?UTF-8?q?fix(=E7=8A=B6=E6=80=81=E7=A0=81):=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=20401=20=E7=9A=84=E6=97=B6=E5=80=99=20HTTP?= =?UTF-8?q?=20=E5=8D=8F=E8=AE=AE=E8=A6=81=E6=B1=82=E5=BF=85=E9=A1=BB?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=BF=94=E5=9B=9E=E4=B8=80=E4=B8=AA=20WWW-Au?= =?UTF-8?q?thenticate=20=E5=A4=B4=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- SUPPLEMENT.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c91c15..1ba4973 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time ### 客户端错误 * 400 **Bad Request** : 请求体包含语法错误 -* 401 **Unauthorized** : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 `403 Forbidden` +* 401 **Unauthorized** : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 `403 Forbidden` ,同时需要返回一个 [`WWW-Authenticate`](https://tools.ietf.org/html/rfc7235#section-4.1) 头 * 403 **Forbidden** : 服务器拒绝执行 * 404 **Not Found** : 找不到目标资源 * 405 **Method Not Allowed** : 不允许执行目标方法,响应中应该带有 `Allow` 头,内容为对该资源有效的 HTTP 方法 diff --git a/SUPPLEMENT.md b/SUPPLEMENT.md index 78efb36..9318ab5 100644 --- a/SUPPLEMENT.md +++ b/SUPPLEMENT.md @@ -6,6 +6,7 @@ * [扩充巴科斯范式](#user-content-扩充巴科斯范式-abnf) * [User-Agent](#user-content-user-agent) +* [WWW-Authenticate 头](#user-content-www-authenticate-头) * [两步验证](#user-content-两步验证) * [同时操作多个资源](#user-content-同时操作多个资源) * [超文本驱动](#user-content-超文本驱动) @@ -53,10 +54,16 @@ obs-fold = CRLF 1*( SP / HTAB ) Android 的网络类型获取可以参考文档:[http://developer.android.com/reference/android/telephony/TelephonyManager.html](http://developer.android.com/reference/android/telephony/TelephonyManager.html) +## WWW-Authenticate 头 + +如果是自定义的身份验证方式,比如要求请求时带上请求头 `Authentication: Token `,那么一般在 token 验证失败返回 `401` 的 `WWW-Authenticate` 头可以是 `WWW-Authenticate: Token` ,当然也可以带上任意其他自定义信息。客户端在发现自己无法识别的信息时应该略过。 + ## 两步验证 如果只是打算简单实现,建议使用 [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/) 。 +关于如何在 API 中实现对两步验证的支持,可以参考 [GitHub 的文档](https://developer.github.com/v3/auth/#working-with-two-factor-authentication)。 + 相关资料: * 这里是一份 TOTP 协议中密码生成算法的简单说明:[http://jacob.jkrall.net/totp/](http://jacob.jkrall.net/totp/) From 9697429842917997a46ded0fa480a24a073ff55c Mon Sep 17 00:00:00 2001 From: c4605 Date: Sat, 5 Nov 2016 21:22:16 +0800 Subject: [PATCH 05/17] =?UTF-8?q?change(=E8=BA=AB=E4=BB=BD=E9=AA=8C?= =?UTF-8?q?=E8=AF=81):=20JWT=20=E7=9A=84=E9=93=BE=E6=8E=A5=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=20OAuth=202.0=20=E9=93=BE=E6=8E=A5=E7=9A=84=E6=AC=A1?= =?UTF-8?q?=E7=BA=A7=EF=BC=8C=E5=9B=A0=E4=B8=BA=20JWT=20=E4=B8=8D=E6=98=AF?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E8=BA=AB=E4=BB=BD=E9=AA=8C=E8=AF=81=E6=96=B9?= =?UTF-8?q?=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1ba4973..cd60db1 100644 --- a/README.md +++ b/README.md @@ -333,14 +333,13 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time 部分接口需要通过某种身份验证方式才能请求成功(这些接口**应该**在文档中标注出来),合适的身份验证解决方案目前有两种: -* [HTTP 基本认证](http://zh.wikipedia.org/wiki/HTTP%E5%9F%BA%E6%9C%AC%E8%AE%A4%E8%AF%81),**只有在部署了 SSL 证书的情况下才可以使用,否则用户密码会有暴露的风险,当然,最好不要使用** -* [JSON Web Token](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25) ,支持通过登录接口使用账号密码获取,在请求接口时使用 `Authorization: Bearer #{token}` 头标或者 `token` 参数的值的方式进行验证。 - * [Json Web Tokens: Introduction](http://angular-tips.com/blog/2014/05/json-web-tokens-introduction/) - * [Json Web Tokens: Examples](http://angular-tips.com/blog/2014/05/json-web-tokens-examples/) - * [Cookies vs Tokens. Getting auth right with Angular.JS](https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/) +* [HTTP 基本认证](http://zh.wikipedia.org/wiki/HTTP%E5%9F%BA%E6%9C%AC%E8%AE%A4%E8%AF%81),**最好只在部署了 SSL 证书的情况下才可以使用,否则用户密码会有暴露的风险** * [OAuth 2.0](https://tools.ietf.org/html/rfc6749) * [官网](http://oauth.net/2/) * [理解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) + * [JSON Web Token](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25) ,一种 Token 的生成标准 + * [Json Web Tokens: Introduction](http://angular-tips.com/blog/2014/05/json-web-tokens-introduction/) + * [Json Web Tokens: Examples](http://angular-tips.com/blog/2014/05/json-web-tokens-examples/) ## 超文本驱动和资源发现 From 467517feb70e826b541498975a92bd997f814797 Mon Sep 17 00:00:00 2001 From: c4605 Date: Sat, 5 Nov 2016 22:18:04 +0800 Subject: [PATCH 06/17] =?UTF-8?q?change(=E5=88=86=E9=A1=B5):=20=E5=AF=B9?= =?UTF-8?q?=20Link=20Header=20=E5=81=9A=E4=BA=86=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=8F=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 不再建议在一个接口里同时实现 page 和 last_cursor * 增加了 rel="url-template:pagination" 的链接,用来让客户端自动生成分页地址 * 使用 `X-Pagination-Info` 替代原来的 `X-Total-Count` --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cd60db1..3f47965 100644 --- a/README.md +++ b/README.md @@ -356,23 +356,22 @@ REST 服务的要求之一就是[超文本驱动](http://roy.gbiv.com/untangled/ ## 分页 -请求某个资源集合时,可以通过指定 `count` 参数来指定每页的资源数量,通过 `page` 参数指定页码,或根据 `last_cursor` 参数指定上一页最后一个资源的标识符。 +请求某个资源集合时,可以通过指定 `count` 参数来指定每页的资源数量,通过 `page` 参数指定页码,或根据需求使用 `last_cursor` 参数指定上一页最后一个资源的标识符替代 `page` 参数。 -如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值 20 , `count` 参数的最大上限为 100 。 +如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值,建议在设计时设置一个最大值。 -如果同时传递了 `last_cursor` 和 `page` 参数,则使用 `page` 。 +分页的相关信息可以包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Pagination-Info` 中( HTTP 头的语法格式可以参考 [ABNF List Extension: #rule](https://tools.ietf.org/html/rfc7230#section-7) )。 -分页的相关信息会包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Total-Count` 中。 - -如果是第一页或者是最后一页时,不会返回 `previous` 和 `next` 的 Link 。 +如果是第一页或者是最后一页时,不返回 `previous` 和 `next` 的 Link 。 ```http HTTP/1.1 200 OK -X-Total-Count: 542 +X-Pagination-Info: count="542" Link: ; rel="first", - ; rel="last" + ; rel="last", ; rel="previous", ; rel="next", + ; rel="url-template:pagination" [ ... From d5fc5b90833e1f54851c3c13134a90545f9eb4fc Mon Sep 17 00:00:00 2001 From: c4605 Date: Sat, 5 Nov 2016 22:18:32 +0800 Subject: [PATCH 07/17] =?UTF-8?q?change(SUPPLEMENT/=E8=B6=85=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E9=A9=B1=E5=8A=A8):=20=E5=A2=9E=E5=8A=A0=E4=BA=86=20s?= =?UTF-8?q?chema=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUPPLEMENT.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SUPPLEMENT.md b/SUPPLEMENT.md index 9318ab5..97438a7 100644 --- a/SUPPLEMENT.md +++ b/SUPPLEMENT.md @@ -153,15 +153,17 @@ HTTP/1.1 204 No Content ## 超文本驱动 -想法受启发于 [JSON API 方案](http://jsonapi.org/),很多做法基本照搬,主要是把 `links` 相关内容放到了请求头里。 +想法受启发于 [JSON API 方案](http://jsonapi.org/),做法基本照搬,主要是把 `links` 相关内容放到了响应头里。 -想法目前还不成熟,并不建议投入使用。 +可以添加 `schema` 参数链接到目标数据的结构描述文档,比如 [JSON Schema](http://json-schema.org/) 、 [Schema.org](http://schema.org/) 等。 + +想法目前还不成熟,不建议投入使用。 ```http HTTP/1.1 200 OK -Link: ; rel="res:author"; allow="collection,get", - ; rel="res:comments"; allow="collection,create,get,delete", - ; rel="res:order"; allow="get,put" +Link: ; rel="url-template:author"; allow="COLLECTION,GET"; schema="...", + ; rel="url-template:comments"; allow="COLLECTION,CREATE,GET,DELETE"; schema="...", + ; rel="url-template:order"; allow="GET,PUT"; schema="..." [{ "id": "1", From 85d93ae1c94eb2bc0bb2a2ac574561347260989a Mon Sep 17 00:00:00 2001 From: c4605 Date: Mon, 7 Nov 2016 00:18:09 +0800 Subject: [PATCH 08/17] =?UTF-8?q?feat(=E6=97=B6=E5=8C=BA):=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E4=BA=86=20`Timezone`=20=E5=A4=B4=E7=9A=84=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3f47965..ba9505f 100644 --- a/README.md +++ b/README.md @@ -111,17 +111,29 @@ language-script-region-variant-extension-privateuse 客户端请求服务器时,如果对时间有特殊要求(如某段时间每天的统计信息),则可以参考 [IETF 相关草案](http://tools.ietf.org/html/draft-sharhalakis-httptz-05) 增加请求头 `Timezone` 。 ``` -Timezone: 2007-06-12T23:48:22+0800 -// OR -Timezone: 1977-07-30T12:00:11+0200;;Europe/Athens +Timezone: 2016-11-06 23:55:52+08:00;;Asia/Shanghai ``` -时区的名称可以参考 [tz datebase](http://www.iana.org/time-zones)([Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) 。 +具体格式说明: -如果客户端请求时没有指定相应的时区,则服务端默认使用 [UTC](http://zh.wikipedia.org/wiki/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6) 时间返回相应数据。 +``` +Timezone: RFC3339 约定的时间格式;POSIX 1003.1 约定的时区字符串;tz datebase 里的时区名称 +``` + +客户端最好提供所有字段,如果没有办法提供,则应该使用空字符串 + +如果客户端请求时没有指定相应的时区,则服务端默认使用最后一次已知时区或者 [UTC](http://zh.wikipedia.org/wiki/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6) 时间返回相应数据。 PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time)这种东西,所以不推荐客户端在请求时使用 Offset 。 +相关资料: + +* [RFC3339](https://tools.ietf.org/html/rfc3339) +* [tz datebase](http://www.iana.org/time-zones) ([Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) +* POSIX 1003.1 时区字符串的说明文档 + * [GNU 的文档](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html) + * [IBM 的文章](https://www.ibm.com/developerworks/aix/library/au-aix-posix/) + ### 时间格式 时间格式遵循 [ISO 8601](https://www.iso.org/obp/ui/#iso:std:iso:8601:ed-3:v1:en)([Wikipedia](https://en.wikipedia.org/wiki/ISO_8601)) 建议的格式: From 4d0086a93ff687bbe35bda5e66f0512936d84866 Mon Sep 17 00:00:00 2001 From: c4605 Date: Mon, 7 Nov 2016 00:19:06 +0800 Subject: [PATCH 09/17] =?UTF-8?q?feat(=E7=8A=B6=E6=80=81=E7=A0=81):=20?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E8=AF=B4=E6=98=8E=E8=BF=94=E5=9B=9E=20`401`?= =?UTF-8?q?=20=E7=8A=B6=E6=80=81=E7=A0=81=E6=97=B6=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?=E4=BB=80=E4=B9=88=E6=83=85=E5=86=B5=E4=B8=8B=E5=BF=85=E9=A1=BB?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=20`WWW-Authenticate`=20=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba9505f..fca2926 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time ### 客户端错误 * 400 **Bad Request** : 请求体包含语法错误 -* 401 **Unauthorized** : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 `403 Forbidden` ,同时需要返回一个 [`WWW-Authenticate`](https://tools.ietf.org/html/rfc7235#section-4.1) 头 +* 401 **Unauthorized** : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 `403 Forbidden` 。如果请求里有 `Authorization` 头,那么必须返回一个 [`WWW-Authenticate`](https://tools.ietf.org/html/rfc7235#section-4.1) 头 * 403 **Forbidden** : 服务器拒绝执行 * 404 **Not Found** : 找不到目标资源 * 405 **Method Not Allowed** : 不允许执行目标方法,响应中应该带有 `Allow` 头,内容为对该资源有效的 HTTP 方法 From 800084d786b57862daa5d02dfb7e3f6c910e1dca Mon Sep 17 00:00:00 2001 From: c4605 Date: Thu, 21 Sep 2017 05:28:11 -0500 Subject: [PATCH 10/17] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fca2926..c9b52eb 100644 --- a/README.md +++ b/README.md @@ -535,3 +535,9 @@ foo({ [iso3166-1]: javascript:; [iso3166-1_wiki]: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + +## 广告 + + + Sponsor + From e83c695d573d222d1e93522621bb9d2909cdc828 Mon Sep 17 00:00:00 2001 From: c4605 Date: Thu, 21 Sep 2017 19:09:07 +0800 Subject: [PATCH 11/17] =?UTF-8?q?feat(=E7=8A=B6=E6=80=81=E7=A0=81):=20?= =?UTF-8?q?=E7=AE=80=E5=8D=95=E4=BB=8B=E7=BB=8D=20303=20=E7=9A=84=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=9C=BA=E6=99=AF=EF=BC=8C=E5=B9=B6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=A0=81=E7=9A=84=E4=B8=A4=E4=B8=AA=E5=8F=82?= =?UTF-8?q?=E8=80=83=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9b52eb..58dafc8 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time * 301 **Moved Permanently** : 被请求的资源已永久移动到新位置 * 302 **Found** : 请求的资源现在临时从不同的 URI 响应请求 -* 303 **See Other** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该使用 `GET` 方法进行请求 +* 303 **See Other** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该使用 `GET` 方法进行请求。比如在创建已经被创建的资源时,可以返回 `303` * 307 **Temporary Redirect** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该保持原有的请求方法进行请求 ### 条件请求 @@ -247,6 +247,8 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time * [维基百科上的《 HTTP 状态码》词条](http://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81) * [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) * [400 vs 422 response to POST of data](http://stackoverflow.com/questions/16133923/400-vs-422-response-to-post-of-data) +* [HTTP Status Codes Decision Diagram – Infographic](https://www.loggly.com/blog/http-status-code-diagram/) +* [HTTP Status Codes](https://httpstatuses.com/) ## 错误处理 From 0d4d36114c008ed041c95595b79ae409a580ae56 Mon Sep 17 00:00:00 2001 From: c4605 Date: Wed, 27 Dec 2017 11:15:30 +0800 Subject: [PATCH 12/17] =?UTF-8?q?change(=E5=B9=BF=E5=91=8A):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=20CodeSponsor=20=E7=9A=84=E5=B9=BF=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 58dafc8..f0c40cd 100644 --- a/README.md +++ b/README.md @@ -537,9 +537,3 @@ foo({ [iso3166-1]: javascript:; [iso3166-1_wiki]: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - -## 广告 - - - Sponsor - From c6a6337bee5d64f8248ee58ca09d53698ecc8a53 Mon Sep 17 00:00:00 2001 From: mingkaili Date: Wed, 5 Sep 2018 15:16:00 +0800 Subject: [PATCH 13/17] fix: OPTIONS typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0c40cd..18f8c10 100644 --- a/README.md +++ b/README.md @@ -160,9 +160,9 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time ## 请求方法 -* 如果请求头中存在 `X-HTTP-Method-Override` 或参数中存在 `_method`(拥有更高权重),且值为 `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTION`, `HEAD` 之一,则视作相应的请求方式进行处理 +* 如果请求头中存在 `X-HTTP-Method-Override` 或参数中存在 `_method`(拥有更高权重),且值为 `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`, `HEAD` 之一,则视作相应的请求方式进行处理 * `GET`, `DELETE`, `HEAD` 方法,参数风格为标准的 `GET` 风格的参数,如 `url?a=1&b=2` -* `POST`, `PUT`, `PATCH`, `OPTION` 方法 +* `POST`, `PUT`, `PATCH`, `OPTIONS` 方法 * 默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的 `Content-Type` 为 `application/json` * 在一些特殊接口中(会在文档中说明),可能允许 `Content-Type` 为 `application/x-www-form-urlencoded` 或者 `multipart/form-data` ,此时请求实体会被视作标准 `POST` 风格的参数进行处理 From 6f943f7ed6b3621c312716ec9339dcfeda692526 Mon Sep 17 00:00:00 2001 From: c4605 Date: Mon, 20 May 2019 12:24:00 +0800 Subject: [PATCH 14/17] =?UTF-8?q?change(=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86&=E5=88=86=E9=A1=B5&=E5=8F=82=E8=80=83=E8=B5=84?= =?UTF-8?q?=E6=96=99):=20=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E4=BB=8E=20README.md=20=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E5=88=B0=E4=BA=86=20SUPPLEMENT.md=20=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20Microsoft=20REST=20API=20Guidelines=20=E7=9A=84?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 152 +++++++------------------------------------------- SUPPLEMENT.md | 133 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 18f8c10..4d0828c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # HTTP 接口设计指北 -* 文档主要目的是为设计接口时提供建议,使大家不必重复造 HTTP 协议已经完成的轮子 -* **只是建议,不是必须遵从的要求** -* 大家有什么问题想法或者建议欢迎 [创建 Issue](https://github.com/bolasblack/http-api-guide/issues/new) 或者 [提交 Pull Request](https://github.com/bolasblack/http-api-guide/compare/) +文档主要目的是为大家在设计接口时提供建议,给大家参考 HTTP 或者其他协议/指南已经设计过的内容 + +**只是建议,不是必须遵从的要求** + +大家有什么问题想法或者建议欢迎 [创建 Issue](https://github.com/bolasblack/http-api-guide/issues/new) 或者 [提交 Pull Request](https://github.com/bolasblack/http-api-guide/compare/) + +* [README.md](.) 主要是简单介绍和列出对设计可能会有帮助的资料,少放一些私货 +* [SUPPLEMENT.md](./SUPPLEMENT.md) 有一些更细节的接口设计方面的我自己的想法,全是私货 ## 目录 @@ -12,10 +17,8 @@ * [国际化](#user-content-国际化) * [请求方法](#user-content-请求方法) * [状态码](#user-content-状态码) -* [错误处理](#user-content-错误处理) * [身份验证](#user-content-身份验证) * [超文本驱动和资源发现](#user-content-超文本驱动和资源发现) -* [分页](#user-content-分页) * [数据缓存](#user-content-数据缓存) * [并发控制](#user-content-并发控制) * [跨域](#user-content-跨域) @@ -53,13 +56,14 @@ HTTP 协议的 2.0 版本还没有正式发布,但目前已经基本稳定下 ## URL -HOST 地址: +URL 的设计都需要遵守 [RFC 3986](http://tools.ietf.org/html/rfc3986) 的的规范。 - https://api.example.com +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) 小节中有说明,本身不限制长度。但是在实践中,服务器和客户端本身会施加限制*,因此需要根据自己的场景和需求做对应的调整 -所有 URI 都需要遵循 [RFC 3986](http://tools.ietf.org/html/rfc3986) 的要求。 +* 比如 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) +* [Microsoft REST API Guidelines - 7.2. URL length](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#72-url-length) -**强烈建议 API 部署 SSL 证书**,这样接口传递的数据的安全性才能都得一定的保障。 +**强烈建议 API 部署 SSL 证书**,这样接口传递的数据的安全性才能获得一定的保障。 ## 空字段 @@ -250,99 +254,6 @@ PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time * [HTTP Status Codes Decision Diagram – Infographic](https://www.loggly.com/blog/http-status-code-diagram/) * [HTTP Status Codes](https://httpstatuses.com/) -## 错误处理 - -在调用接口的过程中,可能出现下列几种错误情况: - -* 服务器维护中,`503` 状态码 - - ```http - HTTP/1.1 503 Service Unavailable - Retry-After: 3600 - Content-Length: 41 - - {"message": "Service In the maintenance"} - ``` - -* 发送了无法转化的请求体,`400` 状态码 - - ```http - HTTP/1.1 400 Bad Request - Content-Length: 35 - - {"message": "Problems parsing JSON"} - ``` - -* 服务到期(比如付费的增值服务等), `403` 状态码 - - ```http - HTTP/1.1 403 Forbidden - Content-Length: 29 - - {"message": "Service expired"} - ``` - -* 因为某些原因不允许访问(比如被 ban ),`403` 状态码 - - ```http - HTTP/1.1 403 Forbidden - Content-Length: 29 - - {"message": "Account blocked"} - ``` - -* 权限不够,`403` 状态码 - - ```http - HTTP/1.1 403 Forbidden - Content-Length: 31 - - {"message": "Permission denied"} - ``` - -* 需要修改的资源不存在, `404` 状态码 - - ```http - HTTP/1.1 404 Not Found - Content-Length: 32 - - {"message": "Resource not found"} - ``` - -* 缺少了必要的头信息,`428` 状态码 - - ```http - HTTP/1.1 428 Precondition Required - Content-Length: 35 - - {"message": "Header User-Agent is required"} - ``` - -* 发送了非法的资源,`422` 状态码 - - ```http - HTTP/1.1 422 Unprocessable Entity - Content-Length: 149 - - { - "message": "Validation Failed", - "errors": [ - { - "resource": "Issue", - "field": "title", - "code": "required" - } - ] - } - ``` - -所有的 `error` 哈希表都有 `resource`, `field`, `code` 字段,以便于定位错误,`code` 字段则用于表示错误类型: - -* `invalid`: 某个字段的值非法,接口文档中会提供相应的信息 -* `required`: 缺失某个必须的字段 -* `not_exist`: 说明某个字段的值代表的资源不存在 -* `already_exist`: 发送的资源中的某个字段的值和服务器中已有的某个资源冲突,常见于某些值全局唯一的字段,比如 @ 用的用户名(这个错误我有纠结,因为其实有 409 状态码可以表示,但是在修改某个资源时,很一般显然请求中不止是一种错误,如果是 409 的话,多种错误的场景就不合适了) - ## 身份验证 部分接口需要通过某种身份验证方式才能请求成功(这些接口**应该**在文档中标注出来),合适的身份验证解决方案目前有两种: @@ -368,35 +279,6 @@ REST 服务的要求之一就是[超文本驱动](http://roy.gbiv.com/untangled/ 目前所知的方案都实现了发现资源的功能,服务端同时需要实现 `OPTIONS` 方法,并在响应中携带 `Allow` 头来告知客户端当前拥有的操作权限。 -## 分页 - -请求某个资源集合时,可以通过指定 `count` 参数来指定每页的资源数量,通过 `page` 参数指定页码,或根据需求使用 `last_cursor` 参数指定上一页最后一个资源的标识符替代 `page` 参数。 - -如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值,建议在设计时设置一个最大值。 - -分页的相关信息可以包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Pagination-Info` 中( HTTP 头的语法格式可以参考 [ABNF List Extension: #rule](https://tools.ietf.org/html/rfc7230#section-7) )。 - -如果是第一页或者是最后一页时,不返回 `previous` 和 `next` 的 Link 。 - -```http -HTTP/1.1 200 OK -X-Pagination-Info: count="542" -Link: ; rel="first", - ; rel="last", - ; rel="previous", - ; rel="next", - ; rel="url-template:pagination" - -[ - ... -] -``` - -相关资料: - -* [RFC 5005 第3节 _Paged Feeds_](http://tools.ietf.org/html/rfc5005#section-3) -* [RFC 5988 6.2.2节 _Initial Registry Contents_](http://tools.ietf.org/html/rfc5988#section-6.2.2) - ## 数据缓存 大部分接口应该在响应头中携带 `Last-Modified`, `ETag`, `Vary`, `Date` 信息,客户端可以在随后请求这些资源的时候,在请求头中使用 `If-Modified-Since`, `If-None-Match` 等请求头来确认资源是否经过修改。 @@ -526,7 +408,13 @@ foo({ 这里还有一些其他参考资料: -* 推荐参考文档 [HTTP API Design Guide](https://github.com/interagent/http-api-design/) 来设计 REST 风格的 API ,只有以下两点我个人并不建议参考: +* [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md) ,很多设计都很有意思,比如: + * [7.10.2. Error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses) + * [9.8. Pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#98-pagination) + * [10. Delta queries](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#10-delta-queries) + * [13. Long running operations](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#13-long-running-operations) +* [GitHub Developer - REST API v3](https://developer.github.com/v3/) +* [HTTP API Design Guide](https://github.com/interagent/http-api-design/) ,有以下两点我个人并不建议参考: * [Use consistent path formats](https://github.com/interagent/http-api-design/#use-consistent-path-formats) 还是不建议将动作写在 URL 中,像文档中的情况,可以将这个行为抽象成一个事务资源 `POST /runs/:run_id/stop-logs` 或者 `POST /runs/:run_id/stoppers` 来解决 * [Paginate with Ranges](https://github.com/interagent/http-api-design/#paginate-with-ranges) diff --git a/SUPPLEMENT.md b/SUPPLEMENT.md index 97438a7..ca34769 100644 --- a/SUPPLEMENT.md +++ b/SUPPLEMENT.md @@ -10,6 +10,8 @@ * [两步验证](#user-content-两步验证) * [同时操作多个资源](#user-content-同时操作多个资源) * [超文本驱动](#user-content-超文本驱动) +* [错误处理](#user-content-错误处理) +* [分页](#user-content-分页) ## 扩充巴科斯范式 (ABNF) @@ -172,3 +174,134 @@ Link: ; rel="url-template:author" "comments": [ "5", "12", "17", "20" ] }] ``` + +## 错误处理 + +在调用接口的过程中,可能出现下列几种错误情况: + +* 服务器维护中,`503` 状态码 + + ```http + HTTP/1.1 503 Service Unavailable + Retry-After: 3600 + Content-Length: 41 + + {"message": "Service In the maintenance"} + ``` + +* 发送了无法转化的请求体,`400` 状态码 + + ```http + HTTP/1.1 400 Bad Request + Content-Length: 35 + + {"message": "Problems parsing JSON"} + ``` + +* 服务到期(比如付费的增值服务等), `403` 状态码 + + ```http + HTTP/1.1 403 Forbidden + Content-Length: 29 + + {"message": "Service expired"} + ``` + +* 因为某些原因不允许访问(比如被 ban ),`403` 状态码 + + ```http + HTTP/1.1 403 Forbidden + Content-Length: 29 + + {"message": "Account blocked"} + ``` + +* 权限不够,`403` 状态码 + + ```http + HTTP/1.1 403 Forbidden + Content-Length: 31 + + {"message": "Permission denied"} + ``` + +* 需要修改的资源不存在, `404` 状态码 + + ```http + HTTP/1.1 404 Not Found + Content-Length: 32 + + {"message": "Resource not found"} + ``` + +* 缺少了必要的头信息,`428` 状态码 + + ```http + HTTP/1.1 428 Precondition Required + Content-Length: 35 + + {"message": "Header User-Agent is required"} + ``` + +* 发送了非法的资源,`422` 状态码 + + ```http + HTTP/1.1 422 Unprocessable Entity + Content-Length: 149 + + { + "message": "Validation Failed", + "errors": [ + { + "resource": "Issue", + "field": "title", + "code": "required" + } + ] + } + ``` + +所有的 `error` 哈希表都有 `resource`, `field`, `code` 字段,以便于定位错误,`code` 字段则用于表示错误类型: + +* `invalid`: 某个字段的值非法,接口文档中会提供相应的信息 +* `required`: 缺失某个必须的字段 +* `not_exist`: 说明某个字段的值代表的资源不存在 +* `already_exist`: 发送的资源中的某个字段的值和服务器中已有的某个资源冲突,常见于某些值全局唯一的字段,比如 @ 用的用户名(这个错误我有纠结,因为其实有 409 状态码可以表示,但是在修改某个资源时,很一般显然请求中不止是一种错误,如果是 409 的话,多种错误的场景就不合适了) + +其他参考: + +* [Microsoft REST API Guidelines - 7.10.2. Error condition responses](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses) +* [GitHub Developer - Client errors](https://developer.github.com/v3/#client-errors) + +## 分页 + +请求某个资源集合时,可以通过指定 `count` 参数来指定每页的资源数量,通过 `page` 参数指定页码,或根据需求使用 `last_cursor` 参数指定上一页最后一个资源的标识符替代 `page` 参数。 + +如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值,建议在设计时设置一个最大值。 + +分页的相关信息可以包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Pagination-Info` 中( HTTP 头的语法格式可以参考 [ABNF List Extension: #rule](https://tools.ietf.org/html/rfc7230#section-7) )。 + +如果是第一页或者是最后一页时,不返回 `previous` 和 `next` 的 Link 。 + +```http +HTTP/1.1 200 OK +X-Pagination-Info: count="542" +Link: ; rel="first", + ; rel="last", + ; rel="previous", + ; rel="next", + ; rel="url-template:pagination" + +[ + ... +] +``` + +相关资料: + +* [RFC 5005 第3节 _Paged Feeds_](http://tools.ietf.org/html/rfc5005#section-3) +* [RFC 5988 6.2.2节 _Initial Registry Contents_](http://tools.ietf.org/html/rfc5988#section-6.2.2) + +其他参考: + +* [Microsoft REST API Guidelines - 9.8. Pagination](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#98-pagination) From 3844bf7c1b89ba2e8b12be0acc2a456ef37b4285 Mon Sep 17 00:00:00 2001 From: c4605 Date: Wed, 15 Apr 2020 15:04:29 +0800 Subject: [PATCH 15/17] =?UTF-8?q?feat(=E5=8F=82=E8=80=83=E8=B5=84=E6=96=99?= =?UTF-8?q?):=20=E6=B7=BB=E5=8A=A0=E4=BA=86=20Standards.REST=20=E7=9A=84?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4d0828c..d2bb276 100644 --- a/README.md +++ b/README.md @@ -403,6 +403,7 @@ foo({ * [Httpbis Status Pages](https://tools.ietf.org/wg/httpbis/) * [所有在 IANA 注册的消息头和相关标准的列表](http://www.iana.org/assignments/message-headers/message-headers.xhtml) +* [Standards.REST](https://standards.rest/) 里面收集了不少对 REST API 设计有借鉴意义的标准和规范 ## 更细节的接口设计指南 From 4b03cc24b758475c44f18e322966ce3bb7e32446 Mon Sep 17 00:00:00 2001 From: c4605 Date: Wed, 15 Apr 2020 15:04:46 +0800 Subject: [PATCH 16/17] =?UTF-8?q?change(=E6=9B=B4=E7=BB=86=E8=8A=82?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=E8=AE=BE=E8=AE=A1=E6=8C=87=E5=8D=97?= =?UTF-8?q?):=20=E6=9B=B4=E5=90=8D=E4=B8=BA=20=E5=85=B6=E4=BB=96=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=AE=BE=E8=AE=A1=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2bb276..3dffbfe 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ * [并发控制](#user-content-并发控制) * [跨域](#user-content-跨域) * [其他资料](#user-content-其他资料) -* [更细节的接口设计指南](#user-content-更细节的接口设计指南) +* [其他接口设计指南](#user-content-其他接口设计指南) ## HTTP 协议 @@ -405,7 +405,7 @@ foo({ * [所有在 IANA 注册的消息头和相关标准的列表](http://www.iana.org/assignments/message-headers/message-headers.xhtml) * [Standards.REST](https://standards.rest/) 里面收集了不少对 REST API 设计有借鉴意义的标准和规范 -## 更细节的接口设计指南 +## 其他接口设计指南 这里还有一些其他参考资料: From cfc1db86ab16e6759a547f2cf2522930de220669 Mon Sep 17 00:00:00 2001 From: c4605 Date: Fri, 14 Nov 2025 22:13:32 +0100 Subject: [PATCH 17/17] =?UTF-8?q?feat:=20add=20"Google=20API=20=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=8C=87=E5=8D=97"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3dffbfe..b1f8779 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,7 @@ foo({ * [Best Practices for Designing a Pragmatic RESTful API](http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api) * [Thoughts on RESTful API Design](http://restful-api-design.readthedocs.org/en/latest/) * [The RESTful CookBook](http://restcookbook.com/) +* [Google API 设计指南](https://docs.cloud.google.com/apis/design) - 这是一份从 2014 年起开始在 Google 内部使用的的 API 设计指南,我没有看完,不过觉得如果问题的时候过去看看找找灵感可能也不错。“本指南是一份活文档,随着时间的推移,我们会采纳和批准新的风格和设计模式,为本指南增加相关内容。本着这种精神,我们会不断完善本指南,并为 API 设计的艺术和技巧提供充足的空间。” [iso3166-1]: javascript:; [iso3166-1_wiki]: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2