## 为什么要用 RESTful RESTful 给我的最大感觉就是规范、易懂和优雅,一个结构清晰、易于理解的 API 完全可以省去许多无意义的沟通和文档。并且 RESTful 现在越来越流行, 在开始介绍 RESTful API 之前,先讲一下 RESTful 架构。 ## RESTful 架构 REST,即Representational State Transfer 的缩写。意为 " 表现层状态转化 " 。 要理解RESTful架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。 ### 资源 (Resources) REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。 所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。所谓"上网",就是与互联网上一系列的"资源"互动,调用它的 URI 。 ### 表现层(Representation) "资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。 比如,文本可以用 txt 格式表现,也可以用 HTML 格式、 XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。 URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的" .html "后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而 URI 应该只代表"资源"的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对"表现层"的描述。 ### 状态转化(State Transfer) 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。 互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。 客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET 、 POST 、 PUT 、 DELETE 。 它们分别对应四种基本操作: GET 用来获取资源, POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。 ### 综述 总结一下什么是RESTful架构: * 每一个URI代表一种资源; * 客户端和服务器之间,传递这种资源的某种表现层; * 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。 ## RESTful API 的设计 介绍完 RESTful 的含义,再说说 RESTful API 的设计。 ### 协议 如果能全站 HTTPS 当然是最好的,不能的话也请尽量将登录、注册等涉及密码的接口使用 HTTPS。 ### 域名 应该尽量将API部署在专用域名之下。如: ~~~ https://api.example.com ~~~ 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 ~~~ https://example.org/api/ ~~~ ### 版本号 API 的版本号和客户端 APP 的版本号是毫无关系的,不要让 APP 将它们用于提交应用市场的版本号传递到服务器,而是提供类似于v1、v2之类的 API 版本号。 版本号拼接在 URL 中。如: ~~~ api.example.com/v1/users ~~~ 或是放在 Header 中: ~~~ api.example.com/users version=v1 ~~~ ### 请求 一般来说 API 的外在形式无非就是增删改查(当然具体的业务逻辑肯定要复杂得多),而查询又分为详情和列表两种,在 RESTful 中这就相当于通用的模板。例如针对文章(Article)设计 API,那么最基础的 URL 就是这几种: * `GET /articles`: 文章列表 * `GET /articles/id`:文章详情 * `POST /articles/`: 创建文章 * `PUT /articles/id`:修改文章 * `DELETE /articles/id`:删除文章 ### Token 和 Sign API 需要设计成无状态,所以客户端在每次请求时都需要提供有效的 Token 和 Sign,在我看来它们的用途分别是: * Token 用于证明请求所属的用户,一般都是服务端在登录后随机生成一段字符串(UUID)和登录用户进行绑定,再将其返回给客户端。Token 的状态保持一般有两种方式实现:一种是在用户每次操作都会延长或重置 TOKEN 的生存时间(类似于缓存的机制),另一种是 Token 的生存时间固定不变,但是同时返回一个刷新用的 Token,当 Token 过期时可以将其刷新而不是重新登录。 * Sign 用于证明该次请求合理,所以一般客户端会把请求参数拼接后并加密作为 Sign 传给服务端,这样即使被抓包了,对方只修改参数而无法生成对应的 Sign 也会被服务端识破。当然也可以将时间戳、请求地址和 Token 也混入 Sign,这样 Sign 也拥有了所属人、时效性和目的地。 ### 业务参数 在 RESTful 的标准中,PUT 和 PATCH 都可以用于修改操作,它们的区别是 PUT 需要提交整个对象,而 PATCH 只需要提交修改的信息。但是在我看来实际应用中不需要这么麻烦,所以我一律使用 PUT,并且只提交修改的信息。 另一个问题是在 POST 创建对象时,究竟该用表单提交更好些还是用 JSON 提交更好些。其实两者都可以,在我看来它们唯一的区别是 JSON 可以比较方便的表示更为复杂的结构(有嵌套对象)。另外无论使用哪种,请保持统一,不要两者混用。 还有一个建议是最好将过滤、分页和排序的相关信息全权交给客户端,包括过滤条件、页数或是游标、每页的数量、排序方式、升降序等,这样可以使 API 更加灵活。但是对于过滤条件、排序方式等,不需要支持所有方式,只需要支持目前用得上的和以后可能会用上的方式即可,并通过字符串枚举解析,这样可见性要更好些。例如: 搜索,客户端只提供关键词,具体搜索的字段,和搜索方式(前缀、全文、精确)由服务端决定: ~~~ /users/?query=ScienJus ~~~ 过滤,只需要对已有的情况进行支持: ~~~ /users/?gender=1 ~~~ 分页: ~~~ /users/?page=2&per_page=20 ~~~ ### 响应 尽量使用 HTTP 状态码,常用的有: * 200:请求成功 * 201:创建、修改成功 * 204:删除成功 * 400:参数错误 * 401:未登录 * 403:禁止访问 * 404:未找到 * 500:系统错误 但是有些时候仅仅使用 HTTP 状态码没有办法明确的表达错误信息,所以也可以在里面再包一层自定义的返回码,例如: 成功时: ~~~ { "code": 100, "msg": "成功", "data": {} } ~~~ ~~~ { "code": -1000, "msg": "用户名或密码错误" } ~~~ data是真正需要返回的数据,并且只会在请求成功时才存在,msg只用在开发环境,并且只为了开发人员识别。客户端逻辑只允许识别code,并且不允许直接将msg的内容展示给用户。如果这个错误很复杂,无法使用一段话描述清楚,也可以在添加一个doc字段,包含指向该错误的文档的链接。 ### 返回数据 JSON 比 XML 可视化更好,也更加节约流量,所以尽量不要使用 XML。 创建和修改操作成功后,需要返回该资源的全部信息。 返回数据不要和客户端界面强耦合,不要在设计 API 时就考虑少查询一张关联表或是少查询 / 返回几个字段能带来多大的性能提升。并且一定要以资源为单位,即使客户端一个页面需要展示多个资源,也不要在一个接口中全部返回,而是让客户端分别请求多个接口。 最好将返回数据进行加密和压缩,尤其是压缩在移动应用中还是比较重要的。