RESTful是Representational State Transfer的缩写,直译为表现层状态转换。它不是一种标准或要求,而是一种设计风格和原则。如果一个API或架构符合REST的原则,我们就可以称之为RESTful API或RESTful Architecture。作为一种流行、成熟的设计原则,RESTful接口是目前不同端 (e.g. Mobile-Backend, Web-Backend)之间广泛利用的交互格式。
基本概念
要理解Representational State Transfer的含义,我们需要把REST拆开,首先要明白什么是资源(Resource),其次是表现层(Representation),最后是状态转换(State Transfer)。厘清了基本概念,才能更好地设计接口。
Representation of Resource
表现层实际指的是Resource的表现层,因此严格来说REST的全称应为The Representational State Transfer of Resource。Resource指的是一个具体的实例对象,比如一段文本,一个JSON对象,一张图片等,某个URI指向这个对象。而表现层就是Resource的表现形式,比如文本的JSON或XML格式。因此在具体的处理上,URI应该仅仅表示资源位置,而其表现形式应该在HTTP的请求头里指定。1
2http://example.com/some_text
Content-Type text/plain
Transfer of State
当客户端与服务器通信时,需要操作Resource使之状态发生变化,即State Transfer。而这种transfer需要通过表现层来实现,通常借助于HTTP协议。参与此过程的各个角色的作用如下:
URI:定位Resource
HTTP动词:Resource将如何进行State Transfer
HTTP请求头:Resource的表现形式
设计原则
版本
接口的开发免不了迭代,因此版本的划分是第一件要考虑的事情。版本的迭代应该尽量做到平滑(smoothed),更新旧接口时不应直接把它干掉,而应该以版本编号来区分。
以下两种方案是处理API版本的常见策略,各有优劣。
- 由URL区分
这个方案直接将版本指定在URL里,比较直观,但是会增加URL的复杂度。1
/api/v1/...
示例
Request1
GET /api/v1/...
Response1
HTTP 1.1 200 OK
- 置于请求/响应头
这个方案将版本信息放在header里,会增加交互双方的处理成本,但是能保持URL的一致。1
2
3/api/...
请求头包含:
Accept application/myapp[.version]
示例
Request1
2
3GET /api/...
请求头包含:
Accept application/myapp.v2
Response1
2
3HTTP 1.1 200 OK
响应头包含:
X-MyApp-Media-Type: myapp.v2
根URL
根URL相当于功夫的起手势,是所有API的根路径,与版本一样通常也有两套方案。
根域名下的路径
1
https://myapp.com/api/
子域名
1
https://api.myapp.com/
此外,访问根URL的时候应该返回Endpoint的列表(Endpoint在下文描述),这样当Developer需要使用你的API时,在文档未完善前,可以访问根URL获取API的基本信息;文档完善时,也可以返回给Developer获取API文档的办法,同时返回的信息本身可以作为一个快速、简要的文档。
Endpoint
在没有RESTful的混沌时期,你可能见过这种“似乎”一目了然的URL:1
GET http://example.com/myapp/getStudent?id=2
或者1
2POST http://example.com/myapp/deleteStudent (id=2)
POST http://example.com/myapp/saveStudent (id=2, name=rob)
然而,这种动宾搭配太啰嗦了,而且完全没有利用HTTP的动词(HTTP动词将在下文解释),估计API的使用者会吐槽得很厉害,其实这是缺乏对Endpoint进行设计的严肃问题。
Endpoint紧随根URL,代表一个Resource或Resource的集合,应当只使用名词,且与数据库表对应起来。此外,应该尽量使用复数形式,因为在英文中,单复数混合作宾语时宾语被认为是复数的。
所以对比起来,1
GET /students
1 | GET /getStudents |
前者显然比后者简洁。
HTTP动词
根URL和Endpoint共同定位了Resource,而如何操作Resource, 由HTTP动词和参数(如有)共同决定。参数的格式通常比较简单(key-value, JSON, 或位于URL路径中),而HTTP动词包括:1
GET [SELECT] 获取resource
1 | POST [CREATE] 新建resource |
1 | PUT [UPDATE] 更新resource, 客户端提供完整的新资源 |
1 | PATCH [UPDATE] 更新resource, 客户端只需要提供改变的属性 |
1 | DELETE [DELETE] 删除resource |
1 | HEAD 获取resource的元数据(metadata) |
1 | OPTIONS 获取关于resource的哪些属性可以被客户端更改的信息 |
最常见的是GET, POST, PUT以及DELETE。
e.g.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21GET /classes 获取所有classes
POST /classes (name=x) 新建class
GET /classes/{id} 根据id获取class
PUT /classes/{id} (name=y, teacher=rob) 更新指定id的class
PATCH /classes/{id} (name=z) 更新指定id的class
DELETE /classes/{id} 根据id删除class
GET /classes/id/students 获取属于指定id的class的所有students
GET /students 获取所有students
POST /students 新建student
GET /students/{id} 根据id获取student
PUT /students/{id} (name=tom, gender=male) 更新指定id的student
PATCH /students/{id} (gender=female) 更新指定id的student
DELETE /students/id 根据id删除指定的student
GET /teachers 获取所有teacher
GET /teachers/{id} 根据id获取teacher
GET /classes/{id}/teachers 获取任教于指定id的class的所有teachers
POST /classes/{cid}/teachers (tid=2) 指派某个teacher(tid)到指定id(cid)的class任教
DELETE /classes/{cid}/teachers (tid=2) 解除某个teacher(tid)在指定id(cid)的class的任职
Filtering
有时我们需要对返回的数据进行过滤,最常见的是在GET请求里,根据请求URL的参数过滤返回结果。
e.g.1
2
3
4
5
6?limit=10
?offset=3
?page=3&rows=10
?sortBy=name&order=desc
?gender=female
?q=searchvalue
API路径和过滤参数的冗余是可以存在的,e.g.1
GET /classes/2/students
1 | GET /students?classId=2 |
Status Codes
HTTP响应信息的状态码同样是RESTful API设计的重要一环,对于成功的请求,不应该偷懒只用200状态码来表示;对于失败的请求,同样不应只用500来表示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18200 OK - [GET] 新建成功
201 CREATED - [POST/PUT/PATCH] 更新成功
202 Accepted - [*] 异步任务, 请求已排队
204 NO CONTENT - [DELETE] 删除成功
400 INVALID REQUEST - [POST/PUT/PATCH] 请求有误
401 Unauthorized - [所有] 无权限(token, 用户名/密码错误等权限认证问题)
403 Forbidden - [所有] 已得到权限, 但访问禁止(权限不够)
404 NOT FOUND - [所有] resource不存在
406 Not Acceptable - [GET] 请求格式不可得, e.g. 请求JSON, 只有XML可用
410 Gone - [GET] resource被永久删除
500 INTERNAL SERVER ERROR - [所有] 服务器内部错误
100-199关于HTTP协议的底层信息,一般不会手动发送。
200-299是请求成功(操作成功或成功获取到所需的数据)的标志。
300-399是为流量重定向准备的,在超媒体相关的API中会使用到。
400-499表示错误信息,比如请求的参数有误,请求的数据不存在等等。
500-599表示服务器错误。
一旦出错(Status Code=4xx),应该返回出错信息,通常使用下面的格式,让API的请求端可以利用(如弹出框提示出错信息):1
2
3{
error: "Invalid API key"
}
响应数据
根据请求的Resource和动词的不同,响应数据的内容也会不同:1
2GET /collection
返回数组
1 | GET /collection/resource |
1 | POST /collection |
1 | PUT /collection/resource |
1 | PATCH /collection/resource |
1 | DELETE /collection/resource |
Protocol
为了安全,应当只使用HTTPS协议。
Authentication
身份认证就拥抱OAuth 2.0框架(e.g. Facebook)吧,这块了解不多就不瞎说了。
Hypermedia API
Github的API就是这种新套路,了解得不够深,不敢妄言。
Reference
Principles of Good RESTful API Design - Thomas Hunter II
理解RESTful架构 - 阮一峰