HTTP RESTful API 规范

使用HTTP API在开发中变得尤为重要,甚至已经作为产品的一部分。对于一个多端产品,和后台的交互主要通过http,于是API变成了最重要的契约。使用RESTFul架构风格,使前后端更解耦,降低沟通API的沟通成本。

一个优雅的RESTful API设计,关系到架构设计和业务开发,也体现工程师的设计能力。REST最大的几个特点为:资源、统一接口、URI和无状态,能理解资源这个概念就理解了RESTful架构的一半。

域名

将API部署在专用域名之下:

https://api.example.com

URL路径

除特殊情况下,必须用URL来作为资源的标识。RESTful架构中,每个网址代表一种资源(resource),必须使用名词表述资源。

可能多个模块有很多接口,为了避免路径冲突,使用两个名词组合:/模块/资源,如:

https://api.example.com/user/user #用户模块的用户
https://api.example.com/user/users # 用户模块的用户列表

HTTP Methods

除个别特殊场景,必须使用标准的HTTP Methods 来表示对资源的操作。
* GET 获取URL定位的资源内容;
* POST 基于URL定位,创建新的资源;
* PUT 基于URL定位的资源,执行创建或更新操作;
* DELETE 基于URL定位的资源,执行删除操作。
GET 方法和查询参数不能改变资源状态,PUT和POST的使用场景严格区分,比如PUT具有幂等性。但实际业务中,并不是所有操作都符合 CRUD 的情况,比如发送邮件,文章发布。遇到这些情况,通常有2种方式:
1. 尽量把动作转为资源,比如点赞start可以作为一个子资源/gists/:id/star
2. 使用POST,但添加控制参数如POST /articles/{:id}?published=true

状态码

2xx 成功/已接受
3xx 重定向
4xx 客户端请求错误,或者可预期的业务错误
5xx 服务端错误

|code|表示状态|说明|客户端支持|服务端支持|
|200|	OK |成功	|	必须	|必须|
|201|	Created |已创建,请求成功,并创建了新的资源,常用于POST 和PUT操作|	建议	|建议|
|202|	Accepted| 已接受	但尚未处理,通常用于异步处理的API	|可选|	建议|
|301|	Moved Permanently| 永久迁移	资源或API被迁移到新的URL | 必须|可选|
|302|	Found| 临时重定向	用于基于业务原因,每次被定向到不同的URI的场景|	必须|可选|
|400|	Bad Request |请求格式错误	例如请求指定Content-Type为application/json, 实际的请求体为form-url-encoded, 服务端应返回400	|必须	|必须|
|401|	Unauthorized |未授权	用户loginToken无效 或 access token无效 |建议	|建议|
|403|	Forbidden| 禁止访问	API 不存在时(路由不到 action时),应使用本错误码;鉴权成功,但无权限操作,也使用本Code。	|建议|建议|
|404|	Not Found| 资源不存在	例如 GET /api/v2.0/user?id=xxx 时,xxx用户不存在,报出 404错误。通常查询一个list, 如果list 不存在,建议返回200, 输出空数组 [ ]。	|建议	|建议|
|405|	Method Not Allowed |不允许该操作,例如某个只读资源,只允许GET操作,但client 执行了put操作,服务端应返回本错误码| 建议|建议|
|406|	Not Acceptable 不可接受|表示 client 请求时Accept 头给定的Content-type、编码等服务端不接受,无法处理。例如Accept期望服务端返回application/xml,但服务端只能处理json格式,报该错误码。	|建议	|建议|
|409|	Conflict	|资源冲突|	建议|	建议|
|410|	Gone|	资源已失效,已过期或已解散。|	建议	|建议|
|415| Unsupported Media Type| 不支持的媒体类型	,表示请求时 Content-Type 指定的类型,服务端不支持。|	建议	| 建议|
|422|	Unprocessable Entity| 无法处理的实体,提交数据校验失败时,使用本错误码。|	必须|	建议|
|500|	Internal Server Error| 服务端内部错误,例如服务内部异常、数据库连接失败等,报出该错误码。	|必须|	必须|

对于可选和建议支持的code,客户端可以采用降级处理的策略,例如201,可以降级视为等同于200 处理。对于4XX的code,如果接口文档没有特殊说明,客户端可将消息中message字段直接显示。对于5XX的code,属服务器内部错误,其message字段客户端只能用作日志及问题调查,不能显示给用户。

HTTP HEADER

客户端、服务端都需要知道互相之间的通讯格式。这些格式可以定义在 HTTP header 里面:
* Content-Type:定义了请求格式
* Accept:定义了接收相应的格式列表

Header除了Content-Type等信息,还可以传递一些公共字段作为公共参数如:token,userId,traceId。对于自定义的Header命名,可以X-开头,虽然这个协议已经在rfc6648中废弃。

在RESTful使用时,通常有4类Header:
* General header:跟传输的数据无关的header或者公共参数
* Request header: 请求需求的header
* Response header: 相应信息,如服务器信息,traceId等
* Entity header: 包含更多和body相关的信息如content length 、 MIME-type.

返回体

 {    
     "code":  32600, 
     "message": "Invalid Request",
     "data":{ }
 }

一般后台的API面对多端,且可能有统一的网关,返回体最好保持某种结构。如上,解释:
* code:具体业务code,状态还是通过http code表示,它只是为了方便定位问题和前端根据code处理更复杂的业务逻辑。
* message:错误信息,这个消息可以是具体错误,但多端交互中,message最好抽象成可以给用户展示的错误。
* data:真正的放回体,错误是为{}

还有一种情况,出错后,前端需要一些“错误信息”继续处理后续业务。比如登录失败5此不能再登录,次数记录是后台返给前端的。参考Google的设计,可以在必要时返回error字段:

{
  "error": {
    "errors": [
      {
        "domain": "global",
        "reason": "badRequest",
        "message": "Bad Request"
      }
    ],
    "code": 400,
    "message": "Bad Request"
  }
}

过滤和分页

实际使用时,后端提供给前端的数据需要搜索、排序、过滤和分页,可以通过请求参数方式实现,最好有一个强制的命名规范,方便各端开发和理解,避免不必要的混乱。比如分页:

/user/users?pageSize=10&pageNum=3

有的情况下,使用传统分页方式会导致列表重复或者丢失,可以通过客户端传上次拿到的最后一个资源id的方式分页

/user/users?pageSize=10&lastId=${lastId}

API 版本

api必须有版本,方便在重大改动时通过版本区分,常用有两种方式。

1. URL方式

/api/v2/user/user?id=1

v2 表示现在是版本2,一般都采用这种方式。

2. hypermedia方式

GET /api/user/user HTTP/1.1
Accept: application/vnd.api.article+xml; version=1.0
CONTENTS