RESTful API最佳实践
Last updated
Was this helpful?
Last updated
Was this helpful?
这篇文章整理内容来自以下的博客文章
不要纠结于无意义的规范
RESTful 真的很好,但它只是一种软件架构风格,过度纠结如何遵守规范只是徒增烦恼,也违背了使用它的初衷。
就像 Elasticsearch 的 API 会在 GET 请求中直接传 JSON,但这是它的业务需要,因为普通的 Query Param 根本无法构造如此复杂的查询 DSL。Github 的 V3 API 中也有很多不符合标准的地方,这也并不会妨碍它成为业界 RESTful API 的参考标准。
接下来要介绍的一些东西也会跟标准不符,但这是在实际开发中遇到过、困扰过、思考过所得出的结论。
为什么要用 RESTful
网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。
因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"API First"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。理解RESTful API请看。
协议
API与用户的通信协议,总是使用HTTPS协议。如果全站不能使用HTTPS的话,也请尽量将登录、注册等涉及密码的接口使用 HTTPS。
域名
应该尽量将API部署在专用域名之下。
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
版本
应该将API的版本号放入URL。
路径
路径又称"终点"(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
还有两个不常用的HTTP动词。
下面是一些例子。
过滤信息
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
状态码
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
错误处理
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
上面代码表示,服务器给出了提示信息,以及文档的网址。
Token 和 Sign
API 需要设计成无状态,所以客户端在每次请求时都需要提供有效的 Token 和 Sign,它们的用途分别是:
Sign 用于证明该次请求合理,所以一般客户端会把请求参数拼接后并加密作为 Sign 传给服务端,这样即使被抓包了,对方只修改参数而无法生成对应的 Sign 也会被服务端识破。当然也可以将时间戳、请求地址和 Token 也混入 Sign,这样 Sign 也拥有了所属人、时效性和目的地。
返回数据
服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
创建和修改操作成功后,需要返回该资源的全部信息。
返回数据不要和客户端界面强耦合,不要在设计 API 时就考虑少查询一张关联表或是少查询 / 返回几个字段能带来多大的性能提升。并且一定要以资源为单位,即使客户端一个页面需要展示多个资源,也不要在一个接口中全部返回,而是让客户端分别请求多个接口。
最好将返回数据进行加密和压缩,尤其是压缩在移动应用中还是比较重要的。
分页
分页布局一般分为两种,一种是在 Web 端比较常见的有底部分页栏的电梯式分页,另一种是在 APP 中比较常见的上拉加载更多的流式分页。这两种分页的 API 到底该如何设计呢?
电梯式分页需要提供page(页数)和pre_page(每页的数量)。例如:
而服务端则需要额外返回total_count(总记录数),以及可选的当前页数、每页的数量(这两个与客户端提交的相同)、总页数、是否有下一页、是否有上一页(这三个都可以通过总记录数计算出)。例如:
流式布局也完全可以使用这种方式,并且不需要查询总记录数(好处是减少一次数据库操作,坏处时客户端需要多请求一次才能判断是否到最后一页)。但是会出现数据重复和缺失的情况,所以更推荐使用游标分页。
游标分页需要提供cursor
(下一页的起点游标) 和limit
(数量) 参数。例如:
如果文章列表默认是以创建时间为倒序排列的,那么cursor
就是当前列表最后一条的创建时间(第一页为当前时间)。
服务端需要返回的数据也很简单,只需要以此游标为起点的总记录数和下一个起点游标就可以了。例如:
如果total
小于limit
,就说明已经没有数据了。
流式布局的分页 API 还有一种情况很常见,就是下拉刷新的增量更新。它的业务逻辑正好和游标分页相反,但是参数基本一样:
返回数据有两种可能,一种是增量更新的数据小于指定的数量,就直接将全部数据返回(这个数量可以设置的相对大一些),客户端会将这些增量更新的数据添加在已有列表的顶部。但是如果增量更新的数据要大于指定的数量,就会只返回最新的 n 条数据作为第一页,这时候客户端需要清空之前的列表。例如:
如果total
大于limit
,说明增量的数据太多所以只返回了第一页,需要清空旧的列表。
另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。采用这种做法。
状态码的完全列表参见。
从上面可以看到,如果想获取当前用户的信息,应该去访问 ,然后就得到了下面结果。
Token 用于证明请求所属的用户,一般都是服务端在登录后随机生成一段字符串(UUID)和登录用户进行绑定,再将其返回给客户端。Token 的状态保持一般有两种方式实现:一种是在用户每次操作都会延长或重置 TOKEN 的生存时间(类似于缓存的机制),另一种是 Token 的生存时间固定不变,但是同时返回一个刷新用的 Token,当 Token 过期时可以将其刷新而不是重新登录。** 身份认证应该使用框架。
详情请参照: