文档主要目的是为大家在设计接口时提供建议,给大家参考 HTTP 或者其他协议/指南已经设计过的内容
只是建议,不是必须遵从的要求
大家有什么问题想法或者建议欢迎 创建 Issue 或者 提交 Pull Request
2014 年 6 月的时候 IETF 已经正式的废弃了 RFC 2616 ,将它拆分为六个单独的协议说明,并重点对原来语义模糊的部分进行了解释:
相关资料:
HTTP 协议的 2.0 版本还没有正式发布,但目前已经基本稳定下来了。
2.0 版本的设计目标是尽量在使用层面上保持与 1.1 版本的兼容,所以,虽然数据交换的格式发生了变化,但语义基本全部被保留下来了。
因此,作为使用者而言,我们并不需要为了支持 2.0 而大幅修改代码。
URL 的设计都需要遵守 RFC 3986 的的规范。
URL 的长度,在 HTTP/1.1: Message Syntax and Routing(RFC 7230) 的 3.1.1 小节中有说明,本身不限制长度。但是在实践中,服务器和客户端本身会施加限制*,因此需要根据自己的场景和需求做对应的调整
large_client_header_buffers
默认值是 8k ,整个 request-line 超过 8k 时就会返回 414 (Request-URI Too Large)强烈建议 API 部署 SSL 证书,这样接口传递的数据的安全性才能获得一定的保障。
接口遵循“输入宽容,输出严格”原则,输出的数据结构中空字段的值一律为 null
RFC 5646 (BCP 47) 规定的语言标签的格式如下:
language-script-region-variant-extension-privateuse
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
部分script
: 这部分使用的是 ISO 15924 (Wikipedia) 中定义的语言代码,比如简体汉字是 zh-Hans
,繁体汉字是 zh-Hant
。region
: 这部分使用的是 ISO 3166-1 (Wikipedia) 中定义的地理区域代码,比如 zh-Hans-CN
就是中国大陆使用的简体中文。variant
: 用来表示 extlang
的定义里没有包含的方言,具体的使用方法可以参考 RFC 5646 。extension
: 用来为自己的应用做一些语言上的额外的扩展,具体的使用方法可以参考 RFC 5646 。privateuse
: 用来表示私有协议中约定的一些语言上的区别,具体的使用方法可以参考 RFC 5646 。其中只有 language
部分是必须的,其他部分都是可选的;不过为了便于编写程序,建议设计接口时约定语言标签的结构,比如统一使用 language-script-region
的形式( zh-Hans-CN
, zh-Hant-HK
等等)。
语言标签是大小写不敏感的,但按照惯例,建议 script
部分首字母大写, region
部分全部大写,其余部分全部小写。
有一点需要注意,任何合法的标签都必须经过 IANA 的认证,已通过认证的标签可以在这个网页查到。此外,网上还有一个非官方的标签搜索引擎。
相关资料:
extlang
)部分的标签介绍有误客户端请求服务器时,如果对时间有特殊要求(如某段时间每天的统计信息),则可以参考 IETF 相关草案 增加请求头 Timezone
。
Timezone: 2016-11-06 23:55:52+08:00;;Asia/Shanghai
具体格式说明:
Timezone: RFC3339 约定的时间格式;POSIX 1003.1 约定的时区字符串;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 )或符号(比如 $ )的歧义。
相关资料:
X-HTTP-Method-Override
或参数中存在 _method
(拥有更高权重),且值为 GET
, POST
, PUT
, DELETE
, PATCH
, OPTIONS
, HEAD
之一,则视作相应的请求方式进行处理GET
, DELETE
, HEAD
方法,参数风格为标准的 GET
风格的参数,如 url?a=1&b=2
POST
, PUT
, PATCH
, OPTIONS
方法
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
相关资料:
GET
成功POST
成功;创建完成后响应头中应该携带头标 Location
,指向新建资源的地址PATCH
, DELETE
成功重定向的新地址都需要在响应头 Location
中返回
GET
方法进行请求。比如在创建已经被创建的资源时,可以返回 303
403 Forbidden
。如果请求里有 Authorization
头,那么必须返回一个 WWW-Authenticate
头Allow
头,内容为对该资源有效的 HTTP 方法Content-Type
中声明格式名称404 Not Found
POST
或者 PUT
请求的消息实体过大Retry-After
头用以标明这个延迟时间(内容可以为数字,单位为秒;或者是一个 HTTP 协议指定的时间格式)。如果没有给出这个 Retry-After
信息,那么客户端应当以处理 500 响应的方式处理它。501
与 405
的区别是:405
是表示服务端不允许客户端这么做,501
是表示客户端或许可以这么做,但服务端还没有实现这个功能
相关资料:
部分接口需要通过某种身份验证方式才能请求成功(这些接口应该在文档中标注出来),合适的身份验证解决方案目前有两种:
REST 服务的要求之一就是超文本驱动,客户端不再需要将某些接口的 URI 硬编码在代码中,唯一需要存储的只是 API 的 HOST 地址,能够非常有效的降低客户端与服务端之间的耦合,服务端对 URI 的任何改动都不会影响到客户端的稳定。
目前有几种方案试图实现这个效果:
目前所知的方案都实现了发现资源的功能,服务端同时需要实现 OPTIONS
方法,并在响应中携带 Allow
头来告知客户端当前拥有的操作权限。
大部分接口应该在响应头中携带 Last-Modified
, ETag
, Vary
, Date
信息,客户端可以在随后请求这些资源的时候,在请求头中使用 If-Modified-Since
, If-None-Match
等请求头来确认资源是否经过修改。
如果资源没有进行过修改,那么就可以响应 304 Not Modified
并且不在响应实体中返回任何内容。
$ 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
$ 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
$ 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
相关资料:
不严谨的实现,或者缺少并发控制的 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相关资料:
接口支持“跨域资源共享”(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-Total-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-Total-Count
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true
如果在任何 GET
请求中带有参数 callback
,且值为非空字符串,那么接口将返回如下格式的数据
$ curl http://api.example.com/#{RESOURCE_URI}?callback=foo
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
})
这里还有一些其他参考资料:
POST /runs/:run_id/stop-logs
或者 POST /runs/:run_id/stoppers
来解决Content-Range
的设计意图,而且有可能和需要使用到 Content-Range
的正常场景冲突(虽然几乎不可能),所以不推荐