前言
现在的网站不再是万维网发明的时候只用于浏览或者收发邮件的功能,而且类似于的app,你可以在上面实现许多功能
RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用
如果一个架构符合REST原则,就称它为RESTful架构。
要理解RESTful,先让我们了解一下REST(Representational State Transfer)表现层状态转换
REST
Resources(资源)
REST是省略了主语,其实应该是(R)REST,资源就是就一是一个具体的信息,可以是文字,音频,视频,字符串等等,每个资源对应一个独一无二的URI,就像图书馆的书有编码一样,那么这个我们就可以找到任何一个资源
Representational(表现层)
表现层是资源的展现形式,一段文字可以TXT\Markdown\HTML\Doc,这个后缀名只是它的表现形式而已,URI并不关心,所以如果你指明后缀,那么请在http请求头中的Accept
和Content-Type
中指定
state Transfer (状态转换)
如果你只是浏览网页而不做操作,那么这个部分也就不重要了,8012年了,怎么可能不操作呢,如果你要操作,那么势必会造成数据和状态的变化。 HTTP协议是一个无状态的协议,那么状态也就必须存储在服务器了,让服务器发生状态变化那么就是REST(表现层状态变化)
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
RESTful API设计
使用CRUD(Create-Read-Update-Delete)
请求类型 | POST(CREATE/增) | GET( REad/查) | PUT(Update/改) | DELETE(DELETE/删) | HEAD | PATCH |
---|---|---|---|---|---|---|
作用 | 在服务器新建一个资源 | 在服务器更新一个资源 | 在服务器更新资源 | 在服务器删除资源 | 获取资源的元数据(比如文件创建时间、大小) | 更新资源的部分属性。 |
例子
资源 | POST(CREATE/增) | GET( REad/查) | PUT(Update/改) | DELETE(DELETE/删) |
---|---|---|---|---|
一组资源的URI,比如https://example.com/resources/ | 在本组资源中创建/追加一个新的资源,该操作往往返回新资源的URL | 列出URI,以及该资源组中每个资源的详细信息(后者可选) | 使用给定的一组资源替换当前整组资源 | 删除整组资源 |
单个资源的URI,比如https://example.com/resources/142 | 把指定的资源当做一个资源组,并在其下创建/追加一个新的元素,使其隶属于当前资源。 | 获取指定的资源的详细信息,格式可以自选一个合适的网络媒体类型(比如:XML、JSON等) | 替换/创建指定的资源。并将其追加到相应的资源组中 | 删除指定的元素 |
返回结果
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
复制代码
域名
使用专用的域名
https://api.example.com
复制代码
版本
把API版本号放入URL中(参考github)
https://api.example.com/v1/
复制代码
路径
URL中不能有动词只能有名词,因为"资源"就是名次,U那么RI不应该有动词,动词应该放在HTTP协议中。
但是总有不符合增删改查的情况,所以我们使用可以使用以下方法
使用post
为需要的动作增加一个 endpoint,使用 POST 来执行动作,比如 POST /resend
重新发送邮件。
增加控制参数
添加动作相关的参数,通过修改参数来控制动作。比如一个博客网站,会有把写好的文章“发布”的功能,可以用上面的 POST /articles/{:id}/publish
方法,也可以在文章中增加 published:boolean
字段,发布的时候就是更新该字段 PUT /articles/{:id}?published=true
把动作转换成可以执行 CRUD 操作的资源
比如“喜欢”一个 gist,就增加一个 /gists/:id/star 子资源,然后对其进行操作:“喜欢”使用 PUT /gists/:id/star,“取消喜欢”使用 DELETE /gists/:id/star 。
另外一个例子是 Fork,这也是一个动作,但是在 gist 下面增加 forks资源,就能把动作变成 CRUD 兼容的:POST /gists/:id/forks 可以执行用户 fork 的动作。
把动词转化为名词
网上汇款,从账户1向账户2汇款500元,错误的URI是:
POST /accounts/1/transfer/500/to/2
复制代码
正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
复制代码
过滤信息
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
复制代码
状态码
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词[*]表示都可以)。
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
复制代码
错误处理
一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{
error: "Invalid API key"
}
复制代码
Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
复制代码
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
复制代码
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
复制代码
上面代码表示,服务器给出了提示信息,以及文档的网址。
限流 rate limit
如果对访问的次数不加控制,很可能会造成 API 被滥用,甚至被 DDos 攻击。根据使用者不同的身份对其进行限流,可以防止这些情况,减少服务器的压力。
对用户的请求限流之后,要有方法告诉用户它的请求使用情况,Github API 使用的三个相关的头部:
X-RateLimit-Limit: 用户每个小时允许发送请求的最大值
X-RateLimit-Remaining:当前时间窗口剩下的可用请求数目
X-RateLimit-Rest: 时间窗口重置的时候,到这个时间点可用的请求数量就会变成 X-RateLimit-Limit 的值
复制代码
如果允许没有登录的用户使用 API(可以让用户试用),可以把 X-RateLimit-Limit 的值设置得很小,比如 Github 使用的 60。没有登录的用户是按照请求的 IP 来确定的,而登录的用户按照认证后的信息来确定身份。
对于超过流量的请求,可以返回 429 Too many requests 状态码,并附带错误信息。而 Github API 返回的是 403 Forbidden,虽然没有 429 更准确,也是可以理解的。
Github 更进一步,提供了不影响当然 RateLimit 的请求查看当前 RateLimit 的接口 GET /rate_limit。
使用JSON,而不是XML
对于响应返回的格式,JSON 因为它的可读性、紧凑性以及多种语言支持等优点,成为了 HTTP API 最常用的返回格式。因此,最好采用 JSON 作为返回内容的格式。如果用户需要其他格式,比如 xml,应该在请求头部 Accept 中指定。对于不支持的格式,服务端需要返回正确的 status code,并给出详细的说明。
验证和授权
一般来说,让任何人随意访问公开的 API 是不好的做法。验证和授权是两件事情:
验证(Authentication)是为了确定用户是其申明的身份,比如提供账户的密码。不然的话,任何人伪造成其他身份(比如其他用户或者管理员)是非常危险的
授权(Authorization)是为了保证用户有对请求资源特定操作的权限。比如用户的私人信息只能自己能访问,其他人无法看到;有些特殊的操作只能管理员可以操作,其他用户有只读的权限等等
需要返回响应的状态码和错误信息, Github API 对某些用户未被授权访问的资源操作返回 404 Not Found,目的是为了防止私有资源的泄露(比如黑客可以自动化试探用户的私有资源,返回 403 的话,就等于告诉黑客用户有这些私有的资源)