1. API的就是程序员的UI,和其他UI一样,必须仔细考虑它的用户体验!
几个必须的原则:
当标准合理的时候遵守标准。
API应该对程序员友好,并且在浏览器地址栏容易输入。
API应该简单,直观,容易使用的同时优雅。
API应该具有足够的灵活性来支持上层ui。
API设计权衡上述几个原则。
2. URL设计
REST的核心原则是将你的API拆分为逻辑上的资源。这些资源通过http被操作(GET ,POST,PUT,DELETE). 显然从API用户的角度来看,”资源“应该是个名词。即使你的内部数据模型和资源已经有了很好的对应,API设计的时候你仍然不需要把它们一对一的都暴露出来。这里的关键是隐藏内部资源,暴露必需的外部资源。
URL遵循动词+宾语的结构
2.1 动词
请求 | 含义 |
---|---|
GET | 读取/Read |
POST | 新建/Create |
PUT | 更新/Update |
PATCH | 部分更新/Update |
DELETE | 删除/Delete |
2.2 宾语
宾语必须为名词且最好为复数,同时要避免多级URL,可通过参数来展示分类. 使用复数使得URL更加规整, 这让API使用者更加容易理解,对开发者来说也更容易实现.
一旦定义好了要暴露的资源,你可以定义资源上允许的操作,以及这些操作和你的API的对应关系:
GET /tickets # 获取所有ticket列表
GET /tickets/12 # 查看某个具体的ticket(id为12)
POST /tickets # 新建一个ticket
PUT /tickets/12 # 更新ticket(id为12)
DELETE /tickets/12 # 删除ticekt(id为12)
可以看出使用REST的好处在于可以充分利用http的强大实现对资源的CURD功能。而这里只需要一个endpoint:/tickets, 再没有其他什么命名规则和url规则了.
2.3 处理资源关联
如何处理关联?关于如何处理资源之间的管理REST原则也有相关的描述:
GET /tickets/12/messages # 获取ticket(id为12)的所有messages列表
GET /tickets/12/messages/5 # 获取ticket(id为12)的某个具体message(id为5)
POST /tickets/12/messages # 为ticket(id为12)新建一个message
PUT /tickets/12/messages/5 # 更新ticket(id为12)的message(id为5)
PATCH /tickets/12/messages/5 # 部分更新ticket(id为12)的message(id为5)
DELETE /tickets/12/messages/5 # 删除ticket(id为12)的message(id为5)
其中,如果这种关联和资源独立,那么可以在资源的输出表示中保存相应资源的endpoint。然后API的使用者就可以通过点击链接找到相关的资源。如果关联和资源联系紧密。资源的输出表示就应该直接保存相应资源信息。(例如这里如果message资源是独立存在的,那么上面 GET /tickets/12/messages就会返回相应message的链接;相反的如果message不独立存在,他和ticket依附存在,则上面的API调用返回直接返回message信息)
2.4 版本化
在API上加入版本信息可以有效的防止用户访问已经更新了的API,同时也能让不同主要版本之间平稳过渡。
版本化API的通常方式有:
2.4.1 URI中设置版本
这种方式通常在URI中增加一段用于标识版本,例如/v1
、/v2
等。例如:
curl https://example.com/api/v2/lists/3
这种方式的优势在于版本信息很容易明显的看出来,可以通过浏览器直接访问。
2.4.2 HTTP头中设置版本
这种方式的版本信息会放在HTTP的请求头中,通常会利用Accept
字段,或者自定义一个字段。例如:
curl https://example.com/api/lists/3 \
-H 'Accept: application/vnd.example.v2+json'
这种方式的好处是当版本升级时,URI保持不变,并且仅用于表示资源定位。
2.4.3 没有版本
版本化的目的是为了标识API的变化,如果API不会变化,或者每次都会重新扩展新的API,这种情况下,就可以标识版本信息。例如:
curl https://example.com/api/lists/3
2.4.4 一种折中方案
前面提到了三种版本化API的方式,通常情况下需要针对自己业务的特殊性来挑选其中的一种方式。但是,在实际应用场景中,情况会更加复杂,API的升级通常有两种情况:
大版本更新,例如字段类型变更、数据对象变更等。这种情况下无法满足对客户端的向下兼容,因此需要修改版本号。
小版本更新,例如增加可选参数、增加返回字段等。这种情况对于新客户端可以增加功能,对于老客户端仍然可以保持原有功能,可以不修改版本号。
因此,折中方案是基于URI中的大版本号和HTTP头中的小版本号整合的方式。下面通过一个简单的示例来解释。
用户管理平台
一个常用的用户管理平台,提供以下API,通过用户ID获取用户信息:
curl https://example.com/api/v1/user/1
...
{
"id": 1,
"name": "test",
"email": "test@example.com"
}
考虑以下两种变动情况:一种是用户id从数字变成了字符串,另一种是新增一个用户头像的值。
前者修改因为数据类型的变化,会导致客户端解析出现问题。因此这样的修改已经破坏了向下兼容性,此时就需要修改API的版本号。例如:
curl https://example.com/api/v2/user/1
...
{
"id": "1",
"name": "test",
"email": "test@example.com"
}
第二种情况,对于旧客户端来说,只是增加了不使用的字段,通常的JSON格式解析库都可以忽略这些不使用的字段。对于新客户端则可以读取新的字段。例如:
curl https://example.com/api/v2/user/1
...
{
"id": "1",
"name": "test",
"email": "test@example.com",
"avatar": "http://example.com/1.jpg"
}
这种情况下,基本可以做到向下兼容,因此可以算是“小版本升级”。针对小版本升级,可以将小版本号放到HTTP头中。例如:
curl https://example.com/api/v2/user/1 \
-H 'API-VERSION: 20170801'
...
{
"id": "1",
"name": "test",
"email": "test@example.com",
"avatar": "http://example.com/1.jpg"
}
2.4.5 后端路由
由于混合版本化的方式同时涉及到URI和HTTP头字段,前端代理(例如HAProxy、nginx)可以通过这些特定版本号字段将请求代理到对应的后端应用。
例如,前端使用HAProxy进行多版本分发,可以针对URI和HTTP头定制acl,然后再对这些acl进行组合,设置不同的backend。
acl is_v1 path_beg /api/v1
acl is_v2 path_beg /api/v2
acl is_version_1 hdr(API-VERSION) 20170801
acl is_version_2 hdr(API-VERSION) 20170701
use_backend old_server if is_v1 is_version_1
use_backend new_server if is_v2 is_version_2
backend old_server
...
backend new_server
...
这样可以将API版本化规则应用到不同的后端,以保证向下兼容性。
2.4.6 总结
基于版本化API规则,将“大版本”应用在URI上,将“小版本”应用在HTTP头字段上。通常来说,如果API升级之后破坏了向下兼容性,就应该升级“大版本”号;如果API升级可以向下兼容,可以升级“小版本”号。
版本化API有很多不同的设计方式,实际应用时,还是要根据业务场景进行选择,包括API版本升级频率,API稳定性等。通过HAProxy、nginx等代理服务,可以在确保向下兼容的情况下,由业务方决定老版本API的保留时间。
URL设计
URL遵循动词+宾语的结构
3.状态码
1xx 相关信息
API不需要1xx状态码
2xx 操作成功
状态码 | 含义 |
---|---|
200 OK | 成功 |
201 Created | 生成了新的资源 |
202 Accepted | 已经收到请求但还未处理完成/异步操作 |
204 No Content | (删除时)资源已不存在 |
3xx 重定向
状态码 | 含义 |
---|---|
301 Moved Permanently | 永久重定向/资源的URL已被更新-API用不到 |
302 | 暂时重定向/用于GET请求-API用不到 |
303 See Other | 暂时重定向/用于POST/PUT/DELETE请求 |
304 Not Modified | 资源未更改/缓存 |
307 | 暂时重定向/用于GET请求-API用不到 |
4xx 客户端错误
状态码 | 含义 |
---|---|
400 Bad Request | 服务器不理解客户端的请求,未做任何处理 |
401 Unauthorized | 用户未提供身份验证凭据,或者没有通过身份验证 |
403 Forbidden | 用户通过了身份验证,但是不具有访问资源所需的权限 |
404 Not Found | 所请求的资源不存在,或不可用 |
405 Method Not Allowed | 用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内 |
406 Not Acceptable | 服务端不支持所需表示/参数格式错误 |
409 Conflict | 通用冲突 |
410 Gone | 所请求的资源已从这个地址转移,不再可用 |
412 Precondition Failed | 前置条件失败(如执行条件更新时的冲突) |
415 Unsupported Media Type | 客户端要求的返回格式不支持 |
422 Unprocessable Entity | 客户端上传的附件无法处理,导致请求失败 |
429 Too Many Requests | 客户端的请求次数超过限额 |
5xx 服务端错误
状态码 | 含义 |
---|---|
500 Internal Server Error | 客户端请求有效,服务器处理时发生了意外 |
503 Service Unavailable | 服务器无法处理请求,一般用于网站维护状态 |
4.服务器回应
API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以服务端返回值的Content-Type属性应为application/json,同时客户端请求头ACCEPT属性应为application/json。
发生错误时,不要返回200状态码,这样相当于状态码失去了意义。应该用状态码表示发生的错误,然后在信息里写明错误详情
提供链接(这个看不懂)
应用范例
this.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
//手动控制HTTP状态码
this.Ctx.ResponseWriter.Write([]byte("byebye"))
//手动控制HTTP返回值
beego.Router("/user/:id([0-9]+",&controllers.UserController{},"GET:Get,PUT:Put,DELETE:Delete")
//同个接口路径不同类型访问导向不同方法