`
hai0378
  • 浏览: 529574 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

HTTP 接口设计指南

 
阅读更多

# HTTP 接口设计指北

 

* 文档主要目的是为设计接口时提供建议,使大家不必重复造 HTTP 协议已经完成的轮子

* **只是建议,不是必须遵从的要求**

* 大家有什么问题想法或者建议欢迎 [创建 Issue](https://github.com/bolasblack/http-api-guide/issues/new) 或者 [提交 Pull Request](https://github.com/bolasblack/http-api-guide/compare/)

 

## 目录

 

* [HTTP 协议](#user-content-http-协议)

* [URL](#user-content-url)

* [空字段](#user-content-空字段)

* [国际化](#user-content-国际化)

* [请求方法](#user-content-请求方法)

* [状态码](#user-content-状态码)

* [错误处理](#user-content-错误处理)

* [身份验证](#user-content-身份验证)

* [超文本驱动和资源发现](#user-content-超文本驱动和资源发现)

* [分页](#user-content-分页)

* [数据缓存](#user-content-数据缓存)

* [并发控制](#user-content-并发控制)

* [跨域](#user-content-跨域)

* [其他资料](#user-content-其他资料)

* [更细节的接口设计指南](#user-content-更细节的接口设计指南)

 

## HTTP 协议

 

### HTTP/1.1

 

2014 年 6 月的时候 IETF 已经正式的废弃了 [RFC 2616](http://tools.ietf.org/html/rfc2616) ,将它拆分为六个单独的协议说明,并重点对原来语义模糊的部分进行了解释:

 

* RFC 7230 - HTTP/1.1: [Message Syntax and Routing](http://tools.ietf.org/html/rfc7230) - low-level message parsing and connection management

* RFC 7231 - HTTP/1.1: [Semantics and Content](http://tools.ietf.org/html/rfc7231) - methods, status codes and headers

* RFC 7232 - HTTP/1.1: [Conditional Requests](http://tools.ietf.org/html/rfc7232) - e.g., If-Modified-Since

* RFC 7233 - HTTP/1.1: [Range Requests](http://tools.ietf.org/html/rfc7233) - getting partial content

* RFC 7234 - HTTP/1.1: [Caching](http://tools.ietf.org/html/rfc7234) - browser and intermediary caches

* RFC 7235 - HTTP/1.1: [Authentication](http://tools.ietf.org/html/rfc7235) - a framework for HTTP authentication

 

相关资料:

 

* [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))

 

### HTTP/2

 

HTTP 协议的 2.0 版本还没有正式发布,但目前已经基本稳定下来了。

 

[2.0 版本的设计目标是尽量在使用层面上保持与 1.1 版本的兼容,所以,虽然数据交换的格式发生了变化,但语义基本全部被保留下来了](http://http2.github.io/http2-spec/index.html#rfc.section.8)。

 

因此,作为使用者而言,我们并不需要为了支持 2.0 而大幅修改代码。

 

* [HTTP/2 latest draft](http://http2.github.io/http2-spec/index.html)

* [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)

* [HTTP/1.1 和 HTTP/2 数据格式的对比](http://http2.github.io/http2-spec/index.html#rfc.section.8.1.3)

 

## URL

 

HOST 地址:

 

    https://api.example.com

 

所有 URI 都需要遵循 [RFC 3986](http://tools.ietf.org/html/rfc3986) 的要求。

 

**强烈建议 API 部署 SSL 证书**,这样接口传递的数据的安全性才能都得一定的保障。

 

## 空字段

 

接口遵循“输入宽容,输出严格”原则,输出的数据结构中空字段的值一律为 `null`

 

## 国际化

 

### 语言标签

 

[RFC 5646](http://tools.ietf.org/html/rfc5646) ([BCP 47](http://tools.ietf.org/html/bcp47)) 规定的语言标签的格式如下:

 

```

language-script-region-variant-extension-privateuse

```

 

1. `language`:这部分使用的是 ISO 639-1, ISO 639-2, ISO 639-3, ISO 639-5 中定义的语言代码,必填

    * 这个部分由 `primary-extlang` 两个部分构成

    * `primary` 部分使用 ISO 639-1, ISO 639-2, ISO 639-3, ISO 639-5 中定义的语言代码,优先使用 ISO 639-1 中定义的条目,比如汉语 `zh`

    * `extlang` 部分是在某些历史性的兼容性的原因,在需要非常细致地区别 `primary` 语言的时候使用,使用 ISO 639-3 中定义的三个字母的代码,比如普通话 `cmn`

    * 虽然 `language` 可以只写 `extlang` 省略 `primary` 部分,但出于兼容性的考虑,还是**建议**加上 `primary` 部分

2. `script`: 这部分使用的是 [ISO 15924](http://www.unicode.org/iso15924/codelists.html) ([Wikipedia](http://zh.wikipedia.org/wiki/ISO_15924)) 中定义的语言代码,比如简体汉字是 `zh-Hans` ,繁体汉字是 `zh-Hant` 。

3. `region`: 这部分使用的是 [ISO 3166-1][iso3166-1] ([Wikipedia][iso3166-1_wiki]) 中定义的地理区域代码,比如 `zh-Hans-CN` 就是中国大陆使用的简体中文。

4. `variant`: 用来表示 `extlang` 的定义里没有包含的方言,具体的使用方法可以参考 [RFC 5646](http://tools.ietf.org/html/rfc5646#section-2.2.5) 。

5. `extension`: 用来为自己的应用做一些语言上的额外的扩展,具体的使用方法可以参考 [RFC 5646](http://tools.ietf.org/html/rfc5646#section-2.2.6) 。

6. `privateuse`: 用来表示私有协议中约定的一些语言上的区别,具体的使用方法可以参考 [RFC 5646](http://tools.ietf.org/html/rfc5646#section-2.2.7) 。

 

其中只有 `language` 部分是必须的,其他部分都是可选的;不过为了便于编写程序,建议设计接口时约定语言标签的结构,比如统一使用 `language-script-region` 的形式( `zh-Hans-CN`, `zh-Hant-HK` 等等)。

 

语言标签是大小写不敏感的,但按照惯例,建议 `script` 部分首字母大写, `region` 部分全部大写,其余部分全部小写。

 

**有一点需要注意,任何合法的标签都必须经过 IANA 的认证,已通过认证的标签可以在[这个网页](http://www.iana.org/assignments/language-subtag-registry)查到。此外,网上还有一个非官方的[标签搜索引擎](http://people.w3.org/rishida/utils/subtags/)。**

 

相关资料:

 

* ISO 639-1 Code List ([Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes))

* [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))

* [ISO 639-3 Code List](http://www-01.sil.org/iso639-3/codes.asp?order=639_3&letter=%25)

* [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))

* [ISO 639-3 Macrolanguage Mappings](http://www-01.sil.org/iso639-3/macrolanguages.asp) ([Wikipedia](https://en.wikipedia.org/wiki/ISO_639_macrolanguage))

* [Relationship between ISO 639-3 and the other parts of ISO 639](http://www-01.sil.org/iso639-3/relationship.asp)

* [网页头部的声明应该是用 lang="zh" 还是 lang="zh-cn"? - 知乎](http://www.zhihu.com/question/20797118)

* [IETF language tag - Wikipedia](https://en.wikipedia.org/wiki/IETF_language_tag)

* [语种名称代码](http://www.ruanyifeng.com/blog/2008/02/codes_for_language_names.html) :文中对带有方言( `extlang` )部分的标签介绍有误

* [Language tags in HTML and XML](http://www.w3.org/International/articles/language-tags/)

* [Choosing a Language Tag](http://www.w3.org/International/questions/qa-choosing-language-tags)

 

### 时区

 

客户端请求服务器时,如果对时间有特殊要求(如某段时间每天的统计信息),则可以参考 [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

```

 

时区的名称可以参考 [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) 时间返回相应数据。

 

PS 考虑到存在[夏时制](https://en.wikipedia.org/wiki/Daylight_saving_time)这种东西,所以不推荐客户端在请求时使用 Offset 。

 

### 时间格式

 

时间格式遵循 [ISO 8601](https://www.iso.org/obp/ui/#iso:std:iso:8601:ed-3:v1:en)([Wikipedia](https://en.wikipedia.org/wiki/ISO_8601)) 建议的格式:

 

* 日期 `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次)

 

相关资料:

 

* [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)

* [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)

 

### 货币名称

 

货币名称可以参考 [ISO 4217](javascript:;)([Wikipedia](http://en.wikipedia.org/wiki/ISO_4217)) 中的约定,标准为货币名称规定了三个字母的货币代码,其中的前两个字母是 [ISO 3166-1][iso3166-1]([Wikipedia][iso3166-1_wiki]) 中定义的双字母国家代码,第三个字母通常是货币的首字母。在货币上使用这些代码消除了货币名称(比如 dollar )或符号(比如 $ )的歧义。

 

相关资料:

 

* 《RESTful Web Services Cookbook 中文版》 3.9 节《如何在表述中使用可移植的数据格式》

 

## 请求方法

 

* 如果请求头中存在 `X-HTTP-Method-Override` 或参数中存在 `_method`(拥有更高权重),且值为 `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTION`, `HEAD` 之一,则视作相应的请求方式进行处理

* `GET`, `DELETE`, `HEAD` 方法,参数风格为标准的 `GET` 风格的参数,如 `url?a=1&b=2`

* `POST`, `PUT`, `PATCH`, `OPTION` 方法

    * 默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的 `Content-Type` 为 `application/json`

    * 在一些特殊接口中(会在文档中说明),可能允许 `Content-Type` 为 `application/x-www-form-urlencoded` 或者 `multipart/form-data` ,此时请求实体会被视作标准 `POST` 风格的参数进行处理

 

关于方法语义的说明:

 

* `OPTIONS` 用于获取资源支持的所有 HTTP 方法

* `HEAD` 用于只获取请求某个资源返回的头信息

* `GET` 用于从服务器获取某个资源的信息

    * 完成请求后返回状态码 `200 OK`

    * 完成请求后需要返回被请求的资源详细信息

* `POST` 用于创建新资源

    * 创建完成后返回状态码 `201 Created`

    * 完成请求后需要返回被创建的资源详细信息

* `PUT` 用于完整的替换资源或者创建指定身份的资源,比如创建 id 为 123 的某个资源

    * 如果是创建了资源,则返回 `201 Created`

    * 如果是替换了资源,则返回 `200 OK`

    * 完成请求后需要返回被修改的资源详细信息

* `PATCH` 用于局部更新资源

    * 完成请求后返回状态码 `200 OK`

    * 完成请求后需要返回被修改的资源详细信息

* `DELETE` 用于删除某个资源

    * 完成请求后返回状态码 `204 No Content`

 

相关资料:

 

* [RFC 7231 中对请求方法的定义](http://tools.ietf.org/html/rfc7231#section-4.3)

* [RFC 5789](http://tools.ietf.org/html/rfc5789) - PATCH 方法的定义

* [维基百科](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)

 

## 状态码

 

### 请求成功

 

* 200 **OK** : 请求执行成功并返回相应数据,如 `GET` 成功

* 201 **Created** : 对象创建成功并返回相应资源数据,如 `POST` 成功;创建完成后响应头中应该携带头标 `Location` ,指向新建资源的地址

* 202 **Accepted** : 接受请求,但无法立即完成创建行为,比如其中涉及到一个需要花费若干小时才能完成的任务。返回的实体中应该包含当前状态的信息,以及指向处理状态监视器或状态预测的指针,以便客户端能够获取最新状态。

* 204 **No Content** : 请求执行成功,不返回相应资源数据,如 `PATCH` , `DELETE` 成功

 

### 重定向

 

**重定向的新地址都需要在响应头 `Location` 中返回**

 

* 301 **Moved Permanently** : 被请求的资源已永久移动到新位置

* 302 **Found** : 请求的资源现在临时从不同的 URI 响应请求

* 303 **See Other** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该使用 `GET` 方法进行请求

* 307 **Temporary Redirect** : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该保持原有的请求方法进行请求

 

### 条件请求

 

* 304 **Not Modified** : 资源自从上次请求后没有再次发生变化,主要使用场景在于实现[数据缓存](#user-content-数据缓存)

* 409 **Conflict** : 请求操作和资源的当前状态存在冲突。主要使用场景在于实现[并发控制](#user-content-并发控制)

* 412 **Precondition Failed** : 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。主要使用场景在于实现[并发控制](#user-content-并发控制)

 

### 客户端错误

 

* 400 **Bad Request** : 请求体包含语法错误

* 401 **Unauthorized** : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 `403 Forbidden`

* 403 **Forbidden** : 服务器拒绝执行

* 404 **Not Found** : 找不到目标资源

* 405 **Method Not Allowed** : 不允许执行目标方法,响应中应该带有 `Allow` 头,内容为对该资源有效的 HTTP 方法

* 406 **Not Acceptable** : 服务器不支持客户端请求的内容格式,但响应里会包含服务端能够给出的格式的数据,并在 `Content-Type` 中声明格式名称

* 410 **Gone** : 被请求的资源已被删除,只有在确定了这种情况是永久性的时候才可以使用,否则建议使用 `404 Not Found`

* 413 **Payload Too Large** : `POST` 或者 `PUT` 请求的消息实体过大

* 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 协议指定的时间格式](http://tools.ietf.org/html/rfc2616#section-3.3))。如果没有给出这个 `Retry-After` 信息,那么客户端应当以处理 500 响应的方式处理它。

 

`501` 与 `405` 的区别是:`405` 是表示服务端不允许客户端这么做,`501` 是表示客户端或许可以这么做,但服务端还没有实现这个功能

 

相关资料:

 

* [RFC 里的状态码列表](http://tools.ietf.org/html/rfc7231#page-49)

* [RFC 4918](http://tools.ietf.org/html/rfc4918) - 422 状态码的定义

* [RFC 6585](http://tools.ietf.org/html/rfc6585) - 新增的四个 HTTP 状态码,[中文版](http://www.oschina.net/news/28660/new-http-status-codes)

* [维基百科上的《 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)

 

## 错误处理

 

在调用接口的过程中,可能出现下列几种错误情况:

 

* 服务器维护中,`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 的话,多种错误的场景就不合适了)

 

## 身份验证

 

部分接口需要通过某种身份验证方式才能请求成功(这些接口**应该**在文档中标注出来),合适的身份验证解决方案目前有两种:

 

* [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/)

* [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)

 

## 超文本驱动和资源发现

 

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-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 的方案

* [Micro API](http://micro-api.org/) ,一种试图与 [JSON-LD](http://json-ld.org/) 兼容的方案

 

目前所知的方案都实现了发现资源的功能,服务端同时需要实现 `OPTIONS` 方法,并在响应中携带 `Allow` 头来告知客户端当前拥有的操作权限。

 

## 分页

 

请求某个资源集合时,可以通过指定 `count` 参数来指定每页的资源数量,通过 `page` 参数指定页码,或根据 `last_cursor` 参数指定上一页最后一个资源的标识符。

 

如果没有传递 `count` 参数或者 `count` 参数的值为空,则使用默认值 20 , `count` 参数的最大上限为 100 。

 

如何同时传递了 `last_cursor` 和 `page` 参数,则使用 `page` 。

 

分页的相关信息会包含在 [Link Header](http://tools.ietf.org/html/rfc5988) 和 `X-Total-Count` 中。

 

如果是第一页或者是最后一页时,不会返回 `previous` 和 `next` 的 Link 。

 

```http

HTTP/1.1 200 OK

X-Total-Count: 542

Link: <http://api.example.com/#{RESOURCE_URI}?last_cursor=&count=100>; rel="first",

      <http://api.example.com/#{RESOURCE_URI}?last_cursor=200&count=100>; rel="last"

      <http://api.example.com/#{RESOURCE_URI}?last_cursor=90&count=100>; rel="previous",

      <http://api.example.com/#{RESOURCE_URI}?last_cursor=120&count=100>; rel="next",

 

[

  ...

]

```

 

相关资料:

 

* [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` 等请求头来确认资源是否经过修改。

 

如果资源没有进行过修改,那么就可以响应 `304 Not Modified` 并且不在响应实体中返回任何内容。

 

```bash

$ curl -i http://api.example.com/#{RESOURCE_URI}

HTTP/1.1 200 OK

Cache-Control: public, max-age=60

Date: Thu, 05 Jul 2012 15:31:30 GMT

Vary: Accept, Authorization

ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"

Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT

 

Content

```

 

```bash

$ 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: public, max-age=60

Date: Thu, 05 Jul 2012 15:31:45 GMT

Vary: Accept, Authorization

Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT

```

 

```bash

$ curl -i http://api.example.com/#{RESOURCE_URI} -H 'If-None-Match: "644b5b0155e6404a9cc4bd9d8b1ae730"'

HTTP/1.1 304 Not Modified

Cache-Control: public, max-age=60

Date: Thu, 05 Jul 2012 15:31:55 GMT

Vary: Accept, Authorization

ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"

Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT

```

 

相关资料:

 

* [RFC 7232](http://tools.ietf.org/html/rfc7232)

* [HTTP 缓存 - Google Developers](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn)

* [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)

* [HTTP 协议中 Vary 的一些研究](https://www.imququ.com/post/vary-header-in-http.html)

* [Cache Control 與 ETag](https://blog.othree.net/log/2012/12/22/cache-control-and-etag/)

 

## 并发控制

 

不严谨的实现,或者缺少并发控制的 `PUT` 和 `PATCH` 请求可能导致 “更新丢失”。这个时候可以使用 `Last-Modified` 和/或 `ETag` 头来实现条件请求,支持乐观并发控制。

 

下文只考虑使用 `PUT` 和 `PATCH` 方法更新资源的情况。

 

* 客户端发起的请求如果没有包含 `If-Unmodified-Since` 或者 `If-Match` 头,那就返回状态码 `403 Forbidden` ,在响应正文中解释为何返回该状态码

* 客户端发起的请求提供的 `If-Unmodified-Since` 或者 `If-Match` 头与服务器记录的实际修改时间或 `ETag` 值不匹配的时候,返回状态码 `412 Precondition Failed`

* 客户端发起的请求提供的 `If-Unmodified-Since` 或者 `If-Match` 头与服务器记录的实际修改时间或 `ETag` 的历史值匹配,但资源已经被修改过的时候,返回状态码 `409 Conflict`

* 客户端发起的请求提供的条件符合实际值,那就更新资源,响应 `200 OK` 或者 `204 No Content` ,并且包含更新过的 `Last-Modified` 和/或 `ETag` 头,同时包含 `Content-Location` 头,其值为更新后的资源 URI

 

相关资料:

 

* 《RESTful Web Services Cookbook 中文版》 10.4 节 《如何在服务器端实现条件 PUT 请求》

* [RFC 7232 "Conditional Requests"](https://tools.ietf.org/html/rfc7232)

* [Location vs. Content-Location](https://www.subbu.org/blog/2008/10/location-vs-content-location)

 

## 跨域

 

### CORS

 

接口支持[“跨域资源共享”(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/)有一些指导性的资料。

 

简单示例:

 

```bash

$ curl -i https://api.example.com -H "Origin: http://example.com"

HTTP/1.1 302 Found

```

 

```bash

$ 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-Total-Count

Access-Control-Allow-Credentials: true

```

 

预检请求的响应示例:

 

```bash

$ 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-Total-Count

Access-Control-Max-Age: 86400

Access-Control-Allow-Credentials: true

```

 

### JSON-P

 

如果在任何 `GET` 请求中带有参数 `callback` ,且值为非空字符串,那么接口将返回如下格式的数据

 

```bash

$ curl http://api.example.com/#{RESOURCE_URI}?callback=foo

```

 

```javascript

foo({

  "meta": {

    "status": 200,

    "X-Total-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

})

```

 

## 其他资料

 

* [Httpbis Status Pages](https://tools.ietf.org/wg/httpbis/)

* [所有在 IANA 注册的消息头和相关标准的列表](http://www.iana.org/assignments/message-headers/message-headers.xhtml)

 

## 更细节的接口设计指南

 

这里还有一些其他参考资料:

 

* 推荐参考文档 [HTTP API Design Guide](https://github.com/interagent/http-api-design/) 来设计 REST 风格的 API ,只有以下两点我个人并不建议参考:

    * [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)

        确实是一个巧妙的设计,但似乎并不符合 `Content-Range` 的设计意图,而且有可能和需要使用到 `Content-Range` 的正常场景冲突(虽然几乎不可能),所以不推荐

* [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/)

 

[iso3166-1]: javascript:;

[iso3166-1_wiki]: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2

 

分享到:
评论

相关推荐

    HTTP 接口设计指北.pdf

    HTTP 接口设计指南 本资源介绍了 HTTP 协议的设计指南,涵盖了 URL、请求方法、状态码、错误处理、身份验证、超文本驱动和资源发现、分页、数据缓存、并发控制、跨域等方面的知识点,旨在帮助开发者设计高效、安全...

    API 接口 设计文档 模板

    API(Application Programming Interface)接口设计文档是软件开发过程中不可或缺的一部分,它为开发者提供了清晰、准确的接口调用指南。一个良好的API文档应包括接口的基本信息、请求方法、请求参数、响应结果等...

    api接口设计.rar

    本资料"api接口设计.rar"可能包含关于如何有效地设计和实现API的详细指南。 首先,API接口设计的基础是明确接口的功能和目标。API可以分为两种主要类型:客户端-服务器API和模块间API。前者通常用于Web服务,如...

    ZLG LwIP的RAW API接口及编程指南

    《ZLG LwIP的RAW API接口及编程指南》是一份深入探讨ZLG LwIP在LM3S M3微控制器上应用的教程资料。LwIP(Lightweight IP)是一个开源的、轻量级的TCP/IP协议栈,设计用于嵌入式系统,尤其适合资源有限的微控制器环境...

    农行网上支付平台-商户接口编程指南-PHP_Edition-V3.1.6.zip

    《农行网上支付平台-商户接口编程指南-PHP_Edition-V3.1.6》是针对农业银行(农行)网上支付平台的一项重要参考资料,专为PHP开发者设计,旨在帮助商户快速理解和集成农行的支付接口。这个版本是2019年的更新,即V...

    http-api-设计指南

    ### HTTP API设计指南知识点概述 #### 基础概念与原则 - **隔离关注点**:在设计HTTP API时,确保请求与响应的不同部分能够清晰地分离,这有助于简化复杂度,使得开发者能够更加专注于核心功能的设计与实现。 - **...

    农行网上支付平台-商户接口编程指南-ASP.NET_Edition-V3.1.6.zip

    综上所述,《农行网上支付平台-商户接口编程指南-ASP.NET_Edition-V3.1.6》涵盖了ASP.NET开发、接口设计、安全实践等多个领域,是开发者实施农行支付集成的重要参考资料。通过深入学习和实践,开发者可以构建出稳定...

    支付宝接口集成指南。

    支付宝接口集成指南是针对开发者和网站运营者提供的一份详细文档,旨在帮助他们将支付宝的支付功能无缝地整合到自己的网站或应用中。这涉及到一系列的技术步骤和注意事项,旨在确保安全、高效的在线交易流程。 首先...

    快递api接口调用指南

    - **接口说明**:同步接口设计用于实时查询单个快递单号的状态,返回结果可以是XML、JSON或HTML格式,支持GET和POST请求方式。 - **接口地址**:http://www.kuaidiapi.cn/rest/ - **使用案例**:...

    系统接口设计模板.docx

    综上所述,《系统接口设计模板》不仅为山东新北洋提供了一个标准化的接口设计指南,而且通过详细的规范和要求确保了接口的一致性和高质量。这对于促进团队协作、提高软件开发效率具有重要意义。

    湖南省建筑行业质量安全监管大数据分析平台数据接口指南(V1.4版).docx

    指南可能引用了国家和地方的建筑工程质量、安全相关法规、标准和技术规范,以确保接口设计符合法律法规要求,同时兼容现有的信息化建设成果。 3. 术语定义: - 建筑工程:涵盖房屋建筑、市政基础设施、交通、水利...

    虚拟商品接口协议指南-1.zip

    接口设计通常包含商品选择、用户身份验证、支付处理和充值结果通知等步骤。开发者需要熟悉如何对接商品数据库,实现快速准确的商品匹配,同时确保交易的安全性。 最后,《互亿无线虚拟商品卡密API接口协议指南4.4....

    API数据服务接口开发指南.docx

    接口设计应遵循开放、标准化的原则,以实现不同系统的无缝对接。 - **名词缩写与定义**: - VIN:17位国际车辆识别号码 - TSS:Target-S System,目标系统 - TSM:Target-S Model,目标模型 - CCA:用于拨打给...

    软件设计师考试指南

    2. **系统设计与架构**:讲解如何进行系统设计,如模块划分、接口设计、数据结构与算法的选择,以及软件架构模式(如分层架构、微服务架构)的应用。 3. **编程语言与技术**:涵盖至少一种或多种主流编程语言(如...

    SoupUI接口测试指南

    SoupUI是一款非常流行的开源接口测试工具,专为Web服务设计。它极大简化了Web服务的测试过程,使测试人员能够专注于测试本身而非繁琐的技术细节。SoupUI支持多种类型的测试,包括但不限于功能测试、负载测试和回归...

    对外接口文档设计

    本文档旨在详细介绍一种标准化、通用性强且易于扩展的对外接口设计方案。 #### 二、核心概念 1. **对外接口(External Interface)**:指企业对外开放的一系列API(应用程序编程接口),用以支持第三方应用或系统...

    服务接口设计文档规范示列

    - RESTful API 设计指南:提供RESTful API的最佳实践。 - JSON 规范:定义了JSON的数据结构和语法。 - 开发者文档:包含项目特定的技术规范和约定。 2 接口列表 本部分列出了服务接口的主要功能,包括货品管理的...

    基于WebService技术的网络管理接口定义指南.pdf

    安全是网络管理接口设计中不可忽视的一个方面。本标准对此也有详细的规定,包括但不限于数据加密、身份验证机制等。 #### 十一、总结 《基于WebService技术的网络管理接口定义指南》不仅为网络管理系统的开发提供了...

Global site tag (gtag.js) - Google Analytics