本文归纳了 RESTful 接口设计实践中的一些经验。

POST 和 PUT

https://stackoverflow.com/questions/630453/what-is-the-difference-between-post-and-put-in-http

PUT 是幂等的,而 POST 不是。 幂等意味着多次相同 PUT 请求之后系统的状态跟一次 PUT 请求的结果相同。

这让我们容易联想到,POST 用于创建资源而 PUT 用于更新资源。 虽然实际项目中大部分情况下确实是这样,但是这个表述是不准确的。 在明确资源名的情况下使用 PUT 请求就可以直接创建该资源,而针对这个资源名的多次 PUT 请求也只会对这同一个资源进行操作,因此是符合幂等的。 因此更准确的表述应该是:POST 用于创建服务端负责分配资源名的资源,而 PUT 请求用于客户端分配或已知资源名的资源的创建和更新。

路径变量与请求参数

路径变量与请求参数是在 URL 中传递数据的两种方式。 它们的区别在于路径变量应当用于指定唯一的资源,而请求参数用于指定筛选资源的条件。

使用路径变量应当能够定位到明确的资源。 路径变量通常是 ID 或者唯一的资源名。 如果没有该资源我们就应该返回 404 错误。

而请求参数筛选到资源可以有多个,也可以没有。 请求参数一般用于筛选某些字段或进行分页。 对于找不到资源的情况我们返回一个空列表就可以了。

PATCH

https://stackoverflow.com/questions/28459418/use-of-put-vs-patch-methods-in-rest-api-real-life-scenarios

https://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

PATCH 用于更新某个资源的部分数据,有别于 PUT 请求必须完整地更新整个资源。 但是需要注意一点,与 PUT 不同,PATCH 没有被要求是幂等的,也就是对同一个资源的相同 PATCH 多次请求可能会导致该资源不断变化。

另外,PATCH 并不是设计来用于真正的 RESTful 接口。 Roy Fielding(HTTP 规范的作者,也是 REST 的发起者)明确说明 PATCH 是用于 HTTP/1.1 的,因为 REST 接口中从来没有部分更新。

PATCH was something I created for the initial HTTP/1.1 proposal because partial PUT is never RESTful. ;-)

但是完整地更新整个资源显然会造成带宽的浪费,因此在实际项目中 PATCH 也时常被混合在 REST 中一起使用。

状态码

状态码实际上非常简单,并不难理解,因此在实际项目中我们就很容易疏忽大意,最后陷入一些误区。 这里介绍一些常见的问题。

常见的误区就是所有的请求成功都返回 200,但是实际上只有返回的 Response 为整个资源时才能使用 200。 而在 PUT、POST 和 DELETE 请求中我们往往不会返回完整的资源,更多的是直接 Response 为空,因此这个时候状态码应当为 204(No Content)。 在 POST 请求(或者用于创建资源的 PUT 请求)成功后,如果需要返回完整的新对象,那么我们应当返回 201(Created)。

不过这些状态码标准在实际项目中不太受到重视。 很多场景下后端都会依据业务来自定义一套新的错误返回机制。 这套机制往往已经包含了足够多的信息,于是错误码就显得不那么重要了。 这是因为客户端报错时往往需要提供很多额外信息,仅用错误码是不够的。 于是就需要一套单独的错误返回机制把一些必要的信息塞在 Response 里返回给客户端。 但这套机制跟错误码是共存的,因此还是建议遵守 RESTful 错误码标准以避免一些理解上的问题。

登录

初学 REST 遇到一个非常操蛋的问题就是登录的接口怎么设计。 不过实际上确实也很操蛋,应当把登录抽象为创建临时的 token,然后 POST 请求拿到 token 即为登录成功。

控制命令

考虑这样一个业务需求:前端需要经过后端查询或改变一些嵌入式设备的运行状态。

比较阳间的思路是把每台设备都抽象成一个资源。 但是实际发现设备向后端暴露的接口非常简洁,基本直接就是对某个寄存器的读写,并且甲方对与前端的显示暂时没有更高的要求,那么这个时候如果我们还强行做抽象就会增加系统的复杂性。 于是我们考虑直接把控制命令抽象为资源,把寄存器的编号扔在它的属性里,极大幅度缩短了开发时间以及跟甲方扯皮的时间。 然而最后的结果是甲方非常开心并且强化了他们的蜜汁自信,把嵌入式那边一顿爆改(甚至直接换成了 json),并且要求前端对数据分类显示。 我的心中只有 mmp。 不过好在按照之前说的设计这部分逻辑就没什么代码,所以也能算不亏,并且甚至可能还避开了甲方的折磨(因为对寄存器操作的封装直接扔给甲方那边的嵌入式了),可以算小赚。

优秀实践

很多时候我们也可以去参考 GitHub REST API