使用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种方式:
- 尽量把动作转为资源,比如点赞start可以作为一个子资源
/gists/:id/star
- 使用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