REST(Representational State Transfer),常见的翻译是“表现层状态转化”。
REST其实省略了主语:资源,表现层实际上指的是资源的表现层。资源是指Web上一切可以识别、可命名、可找到并被处理的实体。比如HTML页面、音频文件、图片等。用一个URI(统一资源定位符)指向资源,使用HTTP请求方法操作资源,URI可以进一步划分为统一资源名(URN,代表资源的名字)和统一资源定位符(URL,代表资源的地址),其中URL可以定位HTTP网址、FTP服务器和文件路径等,符合绝大多数场景,所以一般都可以用URL代替URI。
REST架构风格最重要的架构约束有如下5个:
1. 客户端-服务器端。这种Client/Server的架构形式提供了基本的分布式,客户端发起请求,服务端决定响应或者拒绝请求,如果出错则返回错误信息,由客户端处理异常。
2. 无状态。通信的会话状态应该全部由客户端负责维护,也就是请求种包含了全部必要的信息。如果使用基于服务器端端会话,要么需要保证指定会话会使用同一个服务器响应所有请求,要么得创建一个可供所有服务器访问的公用的会话存储区,对每个请求都额外访问这个集中式的数据储存区获得会话状态。
3. 缓存。无状态就表示可能出现重复的请求,事实上这些请求只需要第一次真正的执行,其余的请求都可以享用这个已完成的结果而直接响应,所以缓存可以抵消一部分无状态带来的影响。
4. 统一接口。统一接口意味着每个REST应用都共享一种通用架构,那些熟悉这种架构的人一眼就能看明白接口的意义,并会继续延承下去。
5. 分层系统图。将系统划分为几个部分,每个部分负责一部分相对单一的职责,然后通过上层对下层的依赖和调用组成一个完整的系统。通常可以划分为如下三层。
- 应用层:负责返回JSON数据和其他业务逻辑
- 服务层:为应用层提供服务支持,如全站的账号系统
- 数据访问层:提供数据访问和存储的服务,如数据库,缓存系统,文件系统,搜索引擎等。
REST就是这一系列设计约束的集合,如果一个架构符合REST原则,就称它为RESTful架构。
API一旦发布其结构将很难修改,因此设计和实现一个符合规范,灵活,友好的API,是一件非常重要的事情。
URI不应该包含动词。动词应该通过不同的HTTP方法来体现,如下是几种常见的错误用法:
正确的用法是:
一定要看请求头信息,并给予正确的状态码。举个例子,假设服务器只能返回JSON格式,如果客户端的头信息的Accept字段要求返回application/xml,这个时候就不应该返回application/json类型的数据,而应该返回406错误。
不能一味使用GET和POST,返回200,不合理的请求方法可能让未来的维护者或者合作方感到迷惑。关于方法语义的说明,可参考下表:
方法 | 语义 |
---|---|
OPTIONS | 用于获取资源支持的所有HTTP方法 |
HEAD | 用户只获取请求某个资源返回的头信息 |
GET | 用于从服务器获取某个资源的信息 1.完成请求后,返回状态码200 OK 2.完成请求后,需要返回被请求的资源详细信息 |
POST | 用于创建新资源: 1.创建完成后,返回状态码201 Created 2.完成请求后,需要返回被创建的资源详细信息 |
PUT | 用于完整的替换资源或者创建指定身份的资源,比如创建id为123的某个资源 1.如果是创建了资源,则返回201 Created 2.如果是替换了资源,则返回200 OK |
PATCH | 用于局部更新资源 1.完成请求后,返回状态码200 OK 2.完成请求后,需要返回被修改的资源的详细信息 |
DELETE | 用于删除某个资源,完成请求后返回状态码204 No Content |
REST服务器是无状态的。在有分页的时候,它并不能知道你当前访问到了什么位置,前一页和后一页的地址是什么,这个关系需要客户端来维护。但是“下一页资源”这样的业务逻辑是需要服务端来提供。例如,下面的例子返回是不完整的:
Status: 200 OK
[
{
"id": 1,
"url": "https://api.linyl.com/users/1/",
},
{
"id": 2,
"url": "https://api.linyl.com/users/2/",
}
]
返回的结果没有告诉我们是否有下一页,也没有告诉我们符合条件的记录总数。可以添加Link和X-Total-Count头来提供这样的功能。
Status: 200 OK
X-Total-Count: 210
Link: ; rel="next",
; rel="last"
[
{
"id": 1,
"url": "https://api.linyl.com/users/1/",
},
{
"id": 2,
"url": "https://api.linyl.com/users/2/",
}
]
rel的值还可以是first、self和prev,客户端只需要根据Link中提供的链接就可以找到全部的符合条件的条目。
还有两处容易出错的地方需要留意:
body中应该直接放数据,不要多层封装。下面有个不恰当的响应的例子:
HTTP/1.1 200 OK
{
'success': trut,
'data': {'id': 1, 'name': xiaoming},
}
直接返回data中的数据就好了:
HTTP/1.1200 OK
{'id': 1, 'name': 'xiaoming}
因为通过状态码“200 OK”就可以知道结果是正确的,就没有必要添加“success”字段。
如果API使用者确实由于某种原因无法访问返回头,或者API需要支持交叉域请求(例如通过jsonp),这两种情况下还是需要包装的。
当访问出错或者响应的结果不符合预期时,不应该返回200作为状态码。哪怕返回的结果中也包含了错误原因,因为在没有充分的文档说明前提下,客户端可能会缓存成功的HTTP请求。
对象应该合理地嵌套,不应该都在一个层次上。如下的格式是不正确的:
{
'id': 1,
'post_id': '1001',
'post_name': 'Post1',
'post_content': 'this is a post'
}
尽可能把相关联的资源信息内联在一起。应该把post作为一个键:
{
'id': 1,
'post': {
'id': '1001',
'name': 'Post1',
'content': 'this is a post'
}
}
常见的区分版本的方法有三种:
https://api.linyl.com/api/v2
。Accept: application/vnd.github.v3+json
。X-Api-Version: 1
。第三种方式不推荐,推荐使用第一种。
随着业务发展,会出现一些API失效或者迁移。对失效的API,应该返回“404 not found“ 或”410 gone“;对迁移的API,返回301重定向。
URI通常最好越简短越好,对结果过滤、排序和搜索相关的功能都应该通过参数实现。一些常见的参数用法如表1.2所示。
参数 | 含义 |
---|---|
offset=0&limit=10 | 指定返回记录的数量,offset也可以用start这个名字 |
offset=10 | 指定返回记录的开始位置 |
page=2 &per_page=100 | 指定第几页,以及每页的 |
sortby=namespace &order=asc | 指定返回结果按照哪个属性排序,以及排序的顺序 |
sort=age,desc | 多个排序条件组合 |
为了避免请求泛滥,给API设置速度限制很重要。为此,RFC6585引入了HTTP状态码429(too many requests)。加入速度限制功能之后,应该提示用户。可以参照GitHup的返回头,如下所述。
HTTP/1.1 200 OK
Date: Mon, 26 Nov 2018 14:24:03 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 1350
Server: GitHub.com
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1543245843
Cache-Control: public, max-age=60, s-maxage=60
Vary: Accept
数据内容在一段时间不会变动,这个时候我们就可以合理地减少HTTP响应内容。应该在响应头中携带Last-Modified、ETag、Vary、Date等信息,客户端可以在随后请求这些资源时,在请求头中使用If-Modified-Since、If-None-Match等来确认资源是否经过修改。如果资源没有做过修改,那么就可以响应“304 Not Modified“,并且不在响应实体中返回任何内容。
缺少并发控制等PUT和PATCH请求可能导致“更新丢失”。这个时候可以使用Last-Modified和ETag头来实现条件请求。具体规则如下: