“一把梭:REST API 全用 POST”

“一把梭:REST API 全用 POST”

写这篇文章的原因主要还是因为V2EX上的这个贴子,这个贴子中说——

“对接同事的接口,他定义的所有接口都是 post 请求,理由是 https 用 post 更安全,之前习惯使用 restful api ,如果说 https 只有 post 请求是安全的话?那为啥还需要 get 、put 、delete ?我该如何反驳他。”

然后该贴中大量的回复大概有这么几种论调,1)POST挺好的,就应该这么干,沟通少,2)一把梭,早点干完早点回家,3)吵赢了又怎么样?工作而已,优雅不能当饭吃。虽然评论没有一边倒,但是也有大量的人支持。然后,我在Twitter上嘲讽了一下,用POST干一切就像看到了来你家装修工人说,“老子干活就是用钉子钉一切,什么螺丝、螺栓、卡扣、插销……通通不用,钉枪一把梭,方便,快捷,安全,干完早回家……不过,还是有一些网友觉得用POST挺好的,而且可以节约时间。所以,正好,我在《我做系统架构的原则》中的“原则五”中反对API返回码无论对错全是200的返回那,我专门写下这一篇文章,以正视听。

这篇文章主要分成下面这几个部分:

  1. 为什么要用不同的HTTP动词?
  2. Restful 进行复杂查询
  3. 几个主要问题的回应
    • POST 更安全吗?
    • 全用 POST 可以节省时间沟通少吗?
    • 早点回家的正确姿势
    • 工作而已,优雅不能当饭吃

为什么要用不同的HTTP动词

编程世界通常来说有两种逻辑:“业务逻辑” 和 “控制逻辑”。

  • 业务逻辑。就是你实现业务需求的功能的代码,就是跟用户需求强相关的代码。比如,把用户提交的数据保存起来,查询用户的数据,完成一个订单交易,为用户退款……等等,这些是业务逻辑
  • 控制逻辑。就是我们用于控制程序运行的非功能性的代码。比如,用于控制程序循环的变量和条件,使用多线程或分布式的技术,使用HTTP/TCP协议,使用什么样数据库,什么样的中间件……等等,这些跟用户需求完全没关系的东西。

网络协议也是一样的,一般来说,几乎所有的主流网络协议都有两个部分,一个是协议头,一个是协议体。协议头中是协议自己要用的数据,协议体才是用户的数据。所以,协议头主要是用于协议的控制逻辑,而协议体则是业务逻辑。

HTTP的动词(或是Method)是在协议头中,所以,其主要用于控制逻辑。

下面是HTTP的动词规范,一般来说,REST API 需要开发人员严格遵循下面的标准规范(参看RFC7231 章节4.2.2 – Idempotent Methods

方法 描述 幂等
GET 用于查询操作,对应于数据库的 select 操作 ✔︎
PUT 用于所有的信息更新,对应于数据库的 update 操作 ✔︎︎
DELETE 用于更新操作,对应于数据库的 delete 操作 ✔︎︎
POST 用于新增操作,对应于数据库的 insert 操作
HEAD 用于返回一个资源对象的“元数据”,或是用于探测API是否健康 ✔︎
PATCH 用于局部信息的更新,对应于数据库的 update 操作
OPTIONS 获取API的相关的信息。 ✔︎

其中,PUT 和 PACTH 都是更新业务资源信息,如果资源对象不存在则可以新建一个,但他们两者的区别是,PUT 用于更新一个业务对象的所有完整信息,就像是我们通过表单提交所有的数据,而 PACTH 则对更为API化的数据更新操作,只需要更需要更新的字段(参看 RFC 5789 )。

当然,现实世界中,可能并不一定严格地按照数据库操作的CRUD来理解API,比如,你有一个登录的API /login 你觉得这个API应该是 GETPOSTPUT 还是 PATCH ?登录的时候用户需要输入用户名和密码,然后跟数据库里的对比(select操作)后反回一个登录的session token,然后这个token作为用户登录的状态令牌。如果按上面表格来说,应该是 select 操作进行 GET ,但是从语义上来说,登录并不是查询信息,应该是用户状态的更新或是新增操作(新增session),所以还是应该使用 POST,而 /logout 你可以使用 DELETE这里相说明一下,不要机械地通过数据库的CRUD来对应这些动词,很多时候,还是要分析一下业务语义。

另外,我们注意到,在这个表格的最后一列中加入了“是否幂等”的,API的幂等对于控制逻辑来说是一件很重要的事。所谓幂等,就是该API执行多次和执行一次的结果是完全一样的,没有副作用。

  • POST 用于新增加数据,比如,新增一个交易订单,这肯定不能是幂等的
  • DELETE 用于删除数据,一个数据删除多次和删除一次的结果是一样的,所以,是幂等的
  • PUT 用于全部数更新,所以,是幂等的。
  • PATCH用于局部更新,比如,更新某个字段 cnt = cnt+1,明显不可能是幂等操作。

幂等这个特性对于远程调用是一件非常关键的事,就是说,远程调用有很多时候会因为网络原因导致调用timeout,对于timeout的请求,我们是无法知道服务端是否已经是收到请求并执行了,此时,我们不能贸然重试请求,对于不是幂等的调用来说,这会是灾难性的。比如像转帐这样的业务逻辑,转一次和转多次结果是不一样的,如果重新的话有可能就会多转了一次。所以,这个时候,如果你的API遵从了HTTP动词的规范,那么你写起程序来就可以明白在哪些动词下可以重试,而在哪些动词下不能重试。如果你把所有的API都用POST来表达的话,就完全失控了。

除了幂等这样的控制逻辑之外,你可能还会有如下的这些控制逻辑的需求:

  • 缓存。通过CDN或是网关对API进行缓存,很显然,我们要在查询GET 操作上建议缓存。
  • 流控。你可以通过HTTP的动词进行更粒度的流控,比如:限制API的请用频率,在读操作上和写操作上应该是不一样的。
  • 路由。比如:写请求路由到写服务上,读请求路由到读服务上。
  • 权限。可以获得更细粒度的权限控制和审计。
  • 监控。因为不同的方法的API的性能都不一样,所以,可以区分做性能分析。
  • 压测。当你需要压力测试API时,如果没有动词的区分的话,我相信你的压力测试很难搞吧。
  • ……等等

也许,你会说,我的业务太简单了,没有必要搞这么复杂。OK,没有问题,但是我觉得你最差的情况下,也是需要做到“读写分离”的,就是说,至少要有两个动词,GET 表示是读操作,POST表示是写操作。

Restful 复杂查询

一般来说,对于查询类的API,主要就是要完成四种操作:排序,过滤,搜索,分页。下面是一些相关的规范。参考于两个我觉得写的最好的Restful API的规范文档,Microsoft REST API GuidelinesPaypal API Design Guidelines

  • 排序。对于结果集的排序,使用 sort 关键字,以及 {field_name}|{asc|desc},{field_name}|{asc|desc} 的相关语法。比如,某API需要返回公司的列表,并按照某些字段排序,如:GET /admin/companies?sort=rank|asc 或是 GET /admin/companies?sort=rank|asc,zip_code|desc

  • 过滤。对于结果集的过滤,使用 filter 关键字,以及 {field_name} op{value} 的语法。比如: GET /companies?category=banking&location=china 。但是,有些时候,我们需要更为灵活的表达式,我们就需要在URL上构造我们的表达式。这里需要定义六个比较操作:=<><=>=,以及三个逻辑操作:andornot。(表达式中的一些特殊字符需要做一定的转义,比如:>= 转成 ge)于是,我们就会有如下的查询表达式:GET /products?$filter=name eq 'Milk' and price lt 2.55 查找所有的价柗小于2.55的牛奶。

  • 搜索。对于相关的搜索,使用 search 关键字,以及关键词。如:GET /books/search?description=algorithm 或是直接就是全文搜索 GET /books/search?key=algorithm 。

  • 分页。对于结果集进行分页处理,分页必需是一个默认行为,这样不会产生大量的返回数据。

    • 使用pageper_page代表页码和每页数据量,比如:GET /books?page=3&per_page=20
    • 可选。上面提到的page方式为使用相对位置来获取数据,可能会存在两个问题:性能(大数据量)与数据偏差(高频更新)。此时可以使用绝对位置来获取数据:事先记录下当前已获取数据里最后一条数据的ID时间等信息,以此获取 “该ID之前的数据” 或 “该时刻之前的数据”。示例:GET /news?max_id=23454345&per_page=20 或 GET /news?published_before=2011-01-01T00:00:00Z&per_page=20

另外,对于一些更为复杂的操作,建议通过分别调用多个API的方式来完成,虽然这样会增加网络请求的次数,但是这样的可以让后端程序和数据耦合度更小,更容易成为微服务的架构。

最后,如果你想在Rest中使用像GraphQL那样的查询语言,你可以考虑一下类似 OData 的解决方案。OData 是 Open Data Protocol 的缩写,最初由 Microsoft 于 2007 年开发。它是一种开放协议,使您能够以简单和标准的方式创建和使用可查询和可互操作的 RESTful API。

几个主要问题的回应

下面是对几个问题的直接回应,如果大家需要我回应更多的问题,可以在后面留言,我会把问题和我的回应添加到下面。

1)为什么API 要Restful,并符合规范?

Restful API算是一个HTTP的规范和标准了,你要说是最佳实践也好,总之,它是一个全世界对HTTP API的一个共识。在这个共识上,你可以无成本地享受很多的技术红利,比如:CDN,API网关,服务治理,监控……等等。这些都是可以让你大幅度降低研发成本,避免踩坑的原因。

2)为什么“过早优化”不适用于API设计?

因为API是一种契约,一旦被使用上,就很难再变更了,就算你发行新的版本的API,你还要驱动各种调用方升级他们的调用方式。所以,接口设计就像数据库模式设计一下,一旦设计好了,未来再变更就比较难了。所以,还是要好好设计。正如前面我给的几个文档——Microsoft REST API GuidelinesPaypal API Design Guidelines 或是 Google API Design Guide 都是让你好好设计API的不错的 Guidelines.

3)POST 更安全吗?

不会。

很多同学以为 GET 的请求数据在URL中,而 POST 的则不是,所以以为 POST 更安全。不是这样的,整个请求的HTTP URL PATH会全部封装在HTTP的协议头中。只要是HTTPS,就是安全的。当然,有些网关如nginx会把URL打到日志中,或是会放在浏览器的历史记录中,所以有人会说 GET 请求不安全,但是,POST 也没有好到哪里去,在 CSRF 这个最常见的安全问题上,则完全就是针对 POST 的。  安全是一件很复杂的事,无论你用哪方法或动词都会不能代表你会更安全。

另外,

4)全用 POST 可以节省时间减少沟通吗?

不但不会,反而更糟糕。

说这种话的人,我感觉是不会思考问题。

  • 其一,为API赋于不同的动词,这个几乎不需要时间。把CRUD写在不同的函数下也是一种很好的编程风格。另外现在几乎所有的开发框架都支持很快速的CRUD的开发,比如Spring Boot,写数据库的CRUD基本上就不需要写SQL语言相关的查询代码,非常之方便。
  • 其二,使用规范的方式,可以节约新加入团队人员的学习成本,而且可以大大减少跨团队的沟能成本。规范和标准其实就是在节约团队时间提升整体效率的,这个我们整个人类进行协作的基础。所以,这个世界上有很多的标准,你只要照着这个标准来,你的所生产的零件就可以适配到其它厂商的产品上。而不需要相互沟通。
  • 萁三,全用POST接口一把梭,不规范不标准,使用你的这个山寨API的人就得来不断的问你,反而增加了沟通。另外,也许你开发业务功能很快了,但是你在做控制逻辑的时候,你就要返工了,从长期上来讲,你的欠下了技术债,这个债反而导致了更大的成本。
5)早点回家的正确姿势

不要以为你回家早就没事了,如果你的代码有这样那样的问题,别人看懂,或是出误用了你的代码出了问题,那么,你早回家有什么意义呢?你一样要被打扰,甚至被叫到公司来处理问题。所以,你应该做的是为了“长期的早回家”,而不是“短期的早回家”,要像长期的早回家,通常来说是这样的:

  • 把代码组织设计好,有更好的扩展性。这样在面对新需求的时候,你就可以做到少改代码,甚至不改代码。这样你才可能早回家。不然,每次需求一来,你得重新写,你怎么可能早回家?
  • 你的代码质量是不错的,有不错的文档和注释。所以,别人不会老有问题来找你,或是你下班后,叫你来处理问题。甚至任何人都可以很容易地接手你的代码,这样你才可能真正不被打扰
6)工作而已,优雅不能当饭吃

回应两点:

其一,遵循个规范而已,把“正常”叫“优雅”,可见标准有多低。这么低的标准也只能“为了吃饭而生存了”。

其二,作为一个“职业程序员”,要学会热爱和尊重自己的职业,热爱自己职业最重要的就是不要让外行人看扁这个职业,自己都不尊重这个职业,你让别人怎么尊重?尊重自己的职业,不仅仅只是能够获得让人羡慕的报酬,而更是要让自己的这个职业的更有含金量

希望大家都能尊重自己从事的这个职业,成为真正的职业化的程序员,而不是一个码农!

你的工作给你权力,而只有你的行为才会给你尊重

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
好烂啊有点差凑合看看还不错很精彩 (159 人打了分,平均分: 4.64 )
Loading...

“一把梭:REST API 全用 POST”》的相关评论

  1. 讲解的很透彻!还有一个支持POST的论点,即GET会被浏览器记录历史,造成泄漏危险。我个人的理解是,需要担忧这种危险的敏感信息,本来就不适合使用GET,比如说账号和密码。而因此把POST的使用范围拓展到所有请求,实乃过犹不及。不知道博主对此有何见解?

    1. API 啊,除了开发者,谁的还会被记录到浏览器历史里去呢?

      另外账号和密码本身不是多敏感的信息,敏感的是它所能打开的数据和操作的大门。所以 GET 获取的数据也可能是很敏感的(比如用户的账单)。

    2. 这属于客户端的数据安全问题了,服务端能帮得上忙的非常有限。

      都能查看浏览器历史记录了,那基本可断定是能查看电脑上所有的cookie、自动密码填充、1password、其它数据资料等等——关键是自己的电脑不能给别人看、自己的电脑不要安装不明软件和插件等等(安全意识差,web服务器或者杀毒软件都帮不了)

      登录接口也不应该属于幂等接口,不应该用GET,GET是幂等的接口,可以被CDN 缓存的

  2. HTTP PUT 和 HTTP PATCH 方法都对应数据库中 update 操作,但是一个是幂等操作,一个是非幂等操作,这里是不是没说清楚啊?我理解的是 PUT 应该基于数据的当前状态,哪怕是用 PUT 来新建数据,多次调用也只会有同一个实体被创建。所以它是幂等的。
    而 PATCH 则不管数据的状态如何,每次操作都会更新数据,这样一来,至少第 1 次和第 N-1 次的结果是不一样的。所以它是非幂等的。
    以上,如有错误,还望指正。

    1. 我文中已经举了例子,PUT是全数据更新,PATCH是局部数据更新。如,cnt= cnt+1 或是扣30元钱的操作,这个是常见的patch操作,不幂等。

      1. 陈老师,您好!在《左耳听风》专栏上,正在看你推荐的《深入理解计算机系统》这本书,在第一章小结中提到三个基本的抽象“1)文件是对I/O设备的抽象;2)虚拟内存是对主存和磁盘的抽象;3)进程是处理器、主存和I/O设备的抽象。”
        如果我们从计算机系统的层面出发,那相对的我们可不可以这样理解:“开发语言(C、C++和Java)就是计算机系统程序命令的抽象!”
        在Linux中一切皆为文件,在计算机系统程序命令中一切皆为二进制,我们之所以用英文编码,是给程序员我们自己看的,最终都会通过编译器转换成机器码也就是二进制0和1。可以这样理解吗?

  3. 我之前接触过的一个公司(我们卖HTTP代理服务产品给他们),所有请求都用Get,包括表单提交,甚至密码提交,在没有HTTP时代时,access.log那个壮观。
    然后就很快的达到URL长度限制2K了,然后看他们内部崩溃……相当奇葩。
    然后下一个公司就纯POST,最小的一个表单大小在16M以上……想想看一个几百M的JSON……发现这个问题是因为HTTP请求我们默认超时是2分钟,然后他们做的这套玩意HTTP请求超时是30分钟……奇葩得接受不了。

  4. 我们团队内部就是全用POST接口。不太赞同你的第二条

    2)全用 POST 可以节省时间减少沟通吗?

    全部使用POST确实可以减少沟通,因为只要在生成文档的时候统一插入一条提示说使用POST就可以了。从而避免无谓的争议。

    你提到为不同的API赋予动词,这个对CRUD来说比较容易,但对一些复杂的业务接口来说就没那么轻松了。而且,跟使用 GET/POST/PUT/DELETE 等HTTP方法相比,我也没看出使用 POST GetXXX/CreateXXX/UpdateXXX/DeleteXXX 有什么大问题。

    你提到了规范性问题。其实,所谓的 REST 风格不过是一种松散的约定,几乎没有强制约束力。我还没见过因为只用 POST 方法就导致系统之间无法对接的情况。就目前而言,有约束力的可能就是 HTTP 协议了。只要支持 HTTP 协议,系统的互通互联就不会有太大的问题。你提到说只用 POST 方法就不规范,甚至是对自己的职业的不敬,确实不敢苟同。

    不过,能过这几年的实践,也发现纯用 POST 的一些问题,借贵宝地分享给大家。

    第一,CDN 节点基本上不会缓存 POST 调用结果,哪怕是查询接口也没法缓存。这一点大家可以见仁见智。有的人觉得动态接口不应该缓存,保证时效;有的人觉得可以缓存一段时间,减少请求量。

    第二,出错之后网关不会重试。大家通常在接口外面加一层网关,网关大都基于 Nginx 之类的 HTTP 代理服务器。但这类网关在遇到 Upstream 的报错的时候通常只会针对 GET 请求做重试。这里也就牵扯出接口幂等的问题。纯用POST方法无法简单识别接口是否幂等。

    其他感觉都还好。如果可以重来,我觉得只使用 GET 和 POST 两种方法,用来区分是否幂等就足够了。

    1. 谢谢你的回复,对你的回复我只会回复一次。苟同不苟同我都无所谓了。

      第一,全世界范围内,所有你看得到的HTTP REST API规范都会要求把遵从HTTP动词规范,我在文中放了一个微软的一个Paypal的,你也可以看看各种开放平台的API的设计。如果,你找到有人推荐全用POST的话,欢迎列出来我看看。

      第二,如果你觉得全用POST没有问题,你不妨把你们公司喜欢的方式也像Microsoft和Paypal那样,写一个XX公司的API设计规范,然后接受大家的反馈,如果你有信心的话,我帮你转发你们公司的规范让更多人看到。

      第三,你在后面也提到POST的一些问题,这就是不走大众标准化带来的问题,你很难跟别人的软件进行协作。

      如果我们的讨论都是在主观的“我觉得”,“我认为”,“感觉还好”……那么没什么好讨论的基础了,因为怎么做你都可以让你的程序跑通,标准再低的草台班子也能让程序跑通,每个人有每个人自己的感受。但是,如果你想讨论的是一种整个行业都在用的标准和规范,一种更高标准的东西,可以做漂亮,做出真正对得你这个这职业的标准,那么我们可以继续讨论。

      1. 呃,稍微打个岔…
        “苟同”是带贬义的自谦用词,这句话补上主语的话是【你】苟不苟同我都无所谓,变成贬低对方了就,当然皓叔肯定是无意的哈。
        另外层主说的“减少沟通”的场景可能是类似于:生产环境部署了带http请求规则的防火墙,并且另有专人运维负责,而运维同事的专业水平实在令人捉急,cd命令都要人教的那种,每次加api还要找他们配patch、delete的规则那简直是折磨,配错了还可能到出了生产事故才会发现,很多时候为了避免再花大量时间精力和这类同事扯皮,就会在自己能控制的地方(代码)来最大限度规避,哪怕这么做使得代码变得越来越不规范。
        开源项目比较没有这种困扰,当然是向规范看齐。但是公司项目的话,也确实不是每个人都能找到能使人愿意在工资范围外投入心力的工作呃…

    2. 我也补充几句,正如你所说,REST风格只是一种松散的约定,你可以不认同,不遵守,你也可以创造另外一种纯POST调用的规范,如:graphql、webservice。

      但无论什么样的规范,至少需要考虑这样几个点:是否优雅具备美感,是否完备自洽,是否足够简单易用,是否具备良好的可扩展性等等。

      大多数团队重新设计一套规范,说好听点是重复造轮子,说难听点就是浪费生命,因为这个轮子并没有解决任何问题,反而可能因为设计的不完善和丑陋,以及相应的文档、工具的支持不到位造成额外的返工、适配、扩展、沟通等工作。

      以最简单的CRUD操作为例,REST风格已经定义好了相应的语义和编码上的规范。你不用HTTP动词去对应这几个操作,也要设计新的规范,如你回复所说的使用 POST GetXXX/CreateXXX/UpdateXXX/DeleteXXX 来解决相同的问题,舍弃REST风格这种由来已久、人尽皆知的规范而选择使用这种临时创造出来的东西,完全看不到任何好处。

      RESTFul与其说是一种规范,不如说是http协议的最佳实践。众所周知,http协议是一个应用协议,但曾经被大量当作是通信协议再使用,所以才会以RESTFul这个名词被单独提出,但它更多是自然而然沉淀出来的一种方法,当然再重新以REST这个名词提出来之后,也做了一定的系统性设计。

  5. “一把梭:REST API 全用 POST”,在之前工作的公司,发现大部分的都是用POST来定义Rest接口。还有一些网关定义接口,竟然只支持POST和GET两种动词,不知这股妖风原来是从所为“安全”、“高效沟通”、“早下班”,实在是可笑。正如之前博主所说,所有的http的状态码:200,然后自己再定义一套code来,来让别人来判断请求是发有问题,真是有“异曲同工”之处。规范,还是要好好学习和参考,切忌不能取其精华拿其糟粕。

  6. 不知道博主对于 GraphQL 怎么看,GraphQL 就是一把梭,甚至所有 HTTP CODE 都是 200。错误要通过检查 HTTP Body 里面是否有 error 字段判断。

    1. GraphQL的问题太多了,具体有这个几个:
      1)数据模型耦合严重,导致后端逻辑会被这个数据模型侵入。
      2)接口契约太过灵活,导致很难控制
      3)一个API干所有的事,无法加缓存
      4)返回的数据中,部分数据可以出错为空。
      5)无法进行监控统计,权限,控制和治理。

      早期做PoC可以,小应用可以,大型的应用玩GraphQL那就自己给自己挖坑了

        1. 个人也对GraphQLl了解的不深,仅针对于论点的来进行探讨,作者抛出了GQL的的五个不足,我也想了解下对此Jackeriss的看法。单纯抛出主观性的结论并且没有对应的论据,很难对问题产生有意义的帮助。
          当然我这条回复涉嫌跑题了

  7. 补充安利下API DESIGN PATTERN这本书,关于REST API说的真的是即透彻,又极具可操作性

  8. 文章的观点有可取之处,但是不喜欢 REST 的朋友用 JSON RPC 即可 ;让大多数人在 REST 上浪费时间,那么你用 JSON RPC 如果能够提高效率的话,会取得巨大的优势

  9. 可选。上面提到的page方式为使用相对位置来获取数据,可能会存在两个问题:性 > 能(大数据量)与数据偏差(高频更新)。此时可以使用绝对位置来获取数据:事
    先记录下当前已获取数据里最后一条数据的ID、时间等信息,以此获取 “该ID之前的
    数据” 或 “该时刻之前的数据”。示例:GET /news?max_id=23454345&per_page=20 或
    GET /news?published_before=2011-01-01T00:00:00Z&per_page=20。

    请问皓哥如果数据发生添加或删除时利用绝对位置不会导致丢失想要的数据吗.
    比如上一次GET了第一页10条数据,最后一条的时间戳为T1=2022-2-13 16:00:00,
    这时第一页某条数据因为某种原因被delete了, 如果用select .. where timestamp > T1
    的话只能取到9条. 是否可以认为这种做法无法做到精确分页,只能获得粗略的分页结果.

  10. 能评价下「 code 全用 200,错误放在 body 里面」吗,V2EX 上面也争论了很久。

  11. 讨论一个 GET 被缓存的问题,看看有没有更好的实践

    按各种规范,GET 是幂等且可以被缓存的。那如果有一个 /get/user/list?page=1&page_size=20 的请求,在首次发起和二次发起之间,原来返回的用户里有一个被删除,如果用 GET 且被中间任意一层缓存了,就拿不到更新后的数据

    实践中试过在参数中加时间戳,强制让每次 GET 的请求参数不一样,从而避开各级缓存让服务端来最后返回(服务端内部的缓存机制怎么响应是 HTTP 协议外了),另一种解决办法就是全走 POST

    1. 有几种解决方案:
      1. 用绝对的位置信息,把primary key,排序,timestamp的信息encode到token里面,把token返回(相当于把cursor,encode)
      2. 还是用page token,但是随机生成一个key作为page token返回,key/value pair存数据库或者redis/memcache里
      3. 还是page token,把page ,pagesize 加timestamp encode到token里。

      我们自己平时用第一种。

    2. 为何要对抗缓存呢?我更喜欢与缓存合作。

      翻页的话,我都是用数据的某种有序属性来定位的,比如 after=xxx。你那样请求的话,没有缓存你也容易出现重复或者遗漏数据呀。

  12. 感觉这是个很好的面试问题,可以问面试者,也可以反过来问面试官,草台班子检测剂

  13. 我们公司架构参考rop 后搞了个根据 markdown接口定义自动生成 spring的接口bean的框架,开发平时在接口系统中先定义接口,请求参数、返回参数,然后一键生成接口bean,框架能根据接口定义自动检验接口数据是否合规。接口开发只要实现业务逻辑就好了,请求dto和返回Dto自动生成,前后端对接直接去看markdown 接口定义就好了,同时给第三方的文档也是直接通过markdown 生成在线文档给出去。整个框架只有一个post出口

  14. 我个人认为关于基于 HTTP API 设计问题的动词分歧主要在于:1)一种将 HTTP 作为一种“文件”资源访问控制协议,所以搭配动词更合适;2)将 HTTP 作为一种 RPC 的载体,这时候全 POST 也无可厚非。
    不过单就“纯正”而言,我更倾向第一种,毕竟 HTTP 名字前面就是 Hyper Text (资源)

      1. “ POST 一把梭” 就是把http当作rpc协议,而不是用restful,这有什么问题呢。当然博主说一定要把api设计成restful,这是另一个问题。

  15. 完全没觉得Restful 有任何优点。HTTP的这些动词很简单很清晰吗?那就不会有那么多人在问PUT和PATCH什么区别啊,什么地方应该用GET还是POST了啊。很显然,对于一个具体的业务场景,到底用什么动词,经常是有争论的,一点沟通成本都不省。对于Restful的“资源”,到底怎样算一个资源,也是各人有各人的看法,完全不存在什么统一。

    当然,用POST一把梭了就不叫Restful了。你在Restful规范里当然看不到POST一把梭的例子。

    1. 问题在于Restful根本不能算是个标准,而且是个残废,导致了各种各样的理解和实现。如果是简单资源crud,那无所谓,但现实世界是复杂的。

      1. 你给的链接是他们普遍遵守的标准,我给了个例外,告诉你他们不拘泥标准,也是随便搞的。有什么问题吗?

          1. 我觉得对简单的业务可能可以用标准Rest接口,但是一些复杂的业务,写的人多了,这个标准很难执行到位。

            比如微信开放平台的API库:

            API名称:取消分阶段发布
            在小程序分阶段发布期间,可以随时调用本接口取消分阶段发布。取消分阶段发布后,受影响的微信用户(即被灰度升级的微信用户)的小程序版本将回退到分阶段发布前的版本

            使用过程中如遇到问题,可在开放平台服务商专区发帖交流。

            请求地址
            GET https://api.weixin.qq.com/wxa/revertgrayrelease?access_token=ACCESS_TOKEN

            —- 这是一个取消操作,理论上不应该用GET

            还有两个相似的接口,一个写成了GET,一个写成了POST:

            1)API名称:查询指定发布审核单的审核状态
            提交审核后,调用本接口可以查询指定发布审核单的审核状态。使用过程中如遇到问题,可在开放平台服务商专区发帖交流。

            请求地址
            POST https://api.weixin.qq.com/wxa/get_auditstatus?access_token=ACCESS_TOKEN

            2)API名称:查询最新一次提交的审核状态
            调用本接口可以查询最新一次提审单的审核状态。使用过程中如遇到问题,可在开放平台服务商专区发帖交流。

            请求地址
            GET https://api.weixin.qq.com/wxa/get_latest_auditstatus?access_token=ACCESS_TOKEN

  16. POST 更安全吗这里有不同意见,post 确实比 get 更安全,当然也只是相对的,如果安全是 1,get 是 0.1,post可能是 0.15 这样。拿 CSRF 举例,如果是个 GET 请求,攻击者只需要在页面里嵌入个 img 让被攻击者访问就可以了,这在大部分网站中都很容易实现,但是如果是 POST,攻击者需要构造自动提交的表单等方式,这对于大部分网站来说基本不可能了。

    1. GET 请求嘛,随便访问,反正服务端不会有啥变化,受 CORS 限制,恶意代码也访问不到返回的数据。(记得不要返回数据的最外层是数组就行?)

  17. 在这个共识上,你可以无成本地享受很多的技术红利,比如:CDN,API网关,服务治理,监控……等等。这块平时接触的不多,看到评论的作者或者读者 能给个具体的案例吗?

  18. 复杂的查询请求使用 GET 实现的话,查询条件需要全都放到 URL 里面。

    如果查询条件很多的话,URL 的长度可能会超出浏览器的限制,对于这样的问题该如何解决呢?

    1. 这个只是个特例,我们讨论的规范是用于绝大多数的情况,不是特例。特例从来都是特殊处理。

      针对你这个例子,1)GET请求最少支持2048个长度,有些浏览器支持8K长度。这个长度绝大多数情况下都够了。2)如果你的查询条件非常复杂,超过了这个长度,那么,我个人认为你可能需要重新review一下你的业务场景,想一想是否需要这样的查询条件,有没有更好的方式。3)换成POST可以缓解这个问题,但POST同样有限制。

  19. 请教一个问题,在实际场景中,可能存在 GET 请求的参数过长导致接口400的情况。这种场景使用 POST 是否合适?

  20. 能不能顺便提一下 对于空接口的处理方式?
    就是找不到用户,应该是返回http200 还是http500?

  21. 对于GET请求参数过长的问题,我有一个想法,就是把非特征的请求参数放到请求头里。比起改成POST,这样我更好接受一点。这样算合理吗?

    我看到的几处文字错误:
    “全用 POST 可以节省时间沟通少吗?”
    有一个”萁三”

  22. REST是面向资源这一点没争议吧?如果你做的接口设计根本不是“面向资源”的(虽然你也是http调用,也用json通讯),但显然你根本不是rest。所以就在讨论之外!如果你的api是“面向资源”设计的,那么是不是如果你接口全部使用post调用就不是rest了呢?我的理解:全部使用post也能是rest。rest实际就是几个约束。为什么说要统一使用(get,post,delete…)呢,就是为了满足“统一接口”这个约束。通过这些http谓语统一对资源的增加修改删除操作。那么能不能在全部使用post,然后将对资源在操作定义到post里面呢?比方说我增加一个http头 {action: get,post,delete},这样是不是也统一了。这也是符合rest定义的,所以关键在于是否“统一了对资源的操作”。甚至rest也没说你非得http协议,对资源的操作就必须是get,post,delete啊。 不过rest几个约束,现在能找到对应并具体实现好的,就是http协议。写这个rest架构毕业论文大神,本身也是写http协议规范的人, 所有我怀疑他在“偷懒”!haha.

  23. 我也确实遇到全用POST的公司,虽然最后妥协了,但是,我是不认同的,理由如下:
    1. 使用GET、POST、DELETE这些请求方法,能够区分出动作,在进行接口问题排查时,就能够清晰知道这个请求的类型了,如果全用POST的话,大概率需要回看源码,才能知道原来这是一个查询操作。
    2. 如果使用GET请求,可能会超过url的长度限制,这个时候,可以考虑使用POST请求,但我觉得,更应该审视你的设计,这么长的url是否可以优化,接口请求数据都是必须的吗?
    3. 在浏览器上,使用GET请求,能够利用上浏览器缓存,而POST是不可以的,这个需要根据实际的场景来选择。
    4. 在浏览器上,使用POST请求,实际发送TCP报文,是拆开HEADER和BODY部分发送的,如果是一些查询的请求,使用GET一次搞定,为什么要用POST(这个我没验证过)?
    5. 在JAVA上,使用POST请求,也增加了请求类的个数,一把梭,肯定类爆炸,不用想,因为我写过一把梭的代码。
    最后,我觉得按照规范来开发,是很有必要的,更有利于代码的阅读与理解。

  24. POST 一把梭哈的原因:
    1. Leader 傻X要求的
    2. 程序员自身水平限制,设计模式、编码规范、RESTFUL、PUT/DELETE 都是个啥?[狗头].gif

  25. “当你需要压力测试API时,如果没有动词的区分的话,我相信你的压力测试很难搞吧。”

    请问这里为什么说很难搞呢?

  26. 有个问题,GraphQL 实现也全是 POST,但它在自己的语法上做了读写分离,这样算好的实践吗?

  27. 分析的很透彻,尤其是最后那句话:遵循个规范而已,把“正常”叫“优雅”,可见标准有多低。这么低的标准也只能“为了吃饭而生存了”。

  28. 说说我自己的切身体会吧,坐标国内某互联网大厂。
    (评论是午休草草写的,大小写和术语不规范请见谅)

    使用 restful 一定程度上会增加沟通成本。
    不知道耗子叔跟前端打交道多不多,不同的开发同学有不同的审美&性格,如果一方不想遵守 restful 风格,另一方想遵守这个风格,那么就需要 battle。而且同事是在不断变化的,总有新人来,每次都重新 battle 不仅影响心情,也会影响开发效率,而且可能制造潜在的矛盾。
    每个人对自己的技术要求或者代码审美是不一样的,没必要因为一个技术观念的不合就去说服对方(可以延拓到非技术领域),或者因此去 judge 对方没有程序员的职业道德。
    restful 不适合 realworld
    realworld 是十分复杂的,restful 提供的原语并不能 cover 真实世界的复杂需求,看了评论区有些小伙伴举的例子问 XXX 该怎么用 restful 风格解决,答案往往需要一些设计 或 workaround,门槛比较高。原因并不在于客观世界的复杂,而在于 restful 并不是一个好的解决方案。
    这里举一个特别简单的例子,如何用 delete 方法删除「一批」资源(批量删除):
    首先标准中没有提到HTTP 的 DELETE 方法是否可以有 body,所以这里你不能使用 json body 的方式提供这一批资源的 ID
    因此你需要先用 POST 方法创建一个「删除一批资源的资源」

    POST /createBatchDelete
    {“ids”: [1,2,3]}

    得到一个 batchDeleteId
    3. 调用 DELETE 方法进行批量删除
    DELETE /deleteBatch?deleteId={batchDeleteId}

    可以看到,一个简单的批量删除需求,使用 RESTful 风格设计 API 是十分荒谬的,不仅后端变复杂,前端也会变得更复杂。

  29. 为什么大多数公司不按Rest的规范来使用HTTP动词?我觉得主要原因是Rest是面向资源的,GET、POST、DELETE、PUT等是对资源的查询、新增、删除、修改。但是我们开发的接口大多是面向过程的。这两种理念上的差别,导致开发的时候很多接口很难与HTTP动词一一对应。就像文中举例的“login”场景,“login”是一个动作,这个动作里面包含了查询(查询用户信息)、新增(新增session或者token),“login”很难对应到一个HTTP动词上。所以如果要使用Rest风格,我们在做接口设计的时候就要转变理念,比如“login”应该是叫“createSession”,“logout”叫“deleteSession”。

  30. 第6点的回应太到位了!但很不幸,略币(码农)驱逐良币(职业程序员),良币不多了。

  31. 当然,有些网关如nginx会把URL打到日志中,或是会放在浏览器的历史记录中,所以有人会说 GET 请求不安全,但是,POST 也没有好到哪里去,在 CSRF 这个最常见的安全问题上,则完全就是针对 POST 的。
    没懂, CSRF 不是针对 Cookie 的吗

  32. 国内的环境还真不好说,之前有个客户用的联通的云服务器,那个服务器的外层网关直接把PUT、DELETE请求都屏蔽了,请求根本进不去,问云平台运维人员,全都大眼瞪小眼,问就是不知道,不清楚,跟问墙壁一样,有什么办法?连夜把请求都改成POST才算结束。

  33. 我开发了个后端程序,查询用get,更新用put,增加用post,开发完前后端联调时,前端不支持put,get不支持url拼接生成,最后我都改成了post

    1. 借楼回复
      如下因别字等原因阅读不舒服的地方. 如更改, 左边为原文. 右侧引号内为修改, 无引号为删除单字
      而 PACTH 则对更为API化的数据更新操作,只需要更需要更新的字段 => 而 PACTH 则”是”更为API化的数据更新操作,只需要”更新”需要更新的字段
      PUT 还是 PATCH ? => PUT 还是 PATCH “?” (输入法切换的锅)
      这里相说明一下 => 这里”想”说明一下
      用于新增加数据 => 用于”增加”数据 OR 用于”新增”数据(倾向)
      用于全部数更新 => 用于”全部”更新(行文风格倾向) OR 用于”全部数据”更新
      这个我们整个人类进行协作的基础 => 这个”是”我们整个人类进行协作的基础
      萁三 => “其”三
      或是出误用了你的代码出了问题 => 或是误用了你的代码出了问题
      要像长期的早回家 => 要”想”长期的早回家

  34. 我挖的坑只要不是我填或者我跑的足够快, 就不是我的问题. 这应该是大多数人的心声.
    工作以来最让自己感动的一句话, 从未见过面的同事在群内: API和文档写得真好, 基本没有疑问的地方.
    而我却觉得这不应该是一件很正常的事么? 高兴个锤子

  35. 往往理想是美好的,现实是残酷的。
    所有的优雅最终都败倒在环境与现实中。能将优雅做到台前的基本上都是大公司,对于功能和设计具有一定的话语权,但还有一大部分人是ToB项目,就如评论区所说,就只开放POST,问就不安全,你可以明显的从沟通中感受到他对此并不了解,但他是甲方。
    做ToB项目的都知道,现在为了避免被审计,一个项目被拆成若干部分被各个公司中标开发,有些公司还在用JDK1.5支撑着古老的程序,比较惨的是他们程序中有个依赖必须各家都得引用,你不得不把JDK11的代码再回退到1.8去兼容。为啥不是他们换?因为其他家也是1.7,1.8。如此问题还有很多。所以,在我看来,这个规范自己玩玩就行,能坚持则坚持,如若不能,饭碗优先。
    引用老罗的一句话:又不是不能用。

  36. 感觉作者有“完美主义”情节,我以前也是,后来被坑惨了,现在做啥都得提醒一下自己:“不要太完美,因为这个世界本来就不完美”。

    就拿作者这篇文章,我就发现有一个问题没答对。

    问题是“为什么“过早优化”不适用于API设计?”。
    但是作者自己的回答却是:因为 API 是一种契约,一旦确定就很难做修改,所以要好好设计。

    这个回答难道不是在鼓励要在一开始就在 API 设计上进行优化?这不是答反了吗?

    再深究这个问题,“过早优化” 是个贬义词吧?“适用” 多是拿好的东西去 “适用”,所以 “过早优化” 当然是 “不适用” 于任何东西的,所以 “过早优化”不适用于API设计 这本身就是在表达一个确定的结论,所以在这前面加上一个为什么这就有点奇怪了,这就相当于是在问:为什么“过度运动”不利于身体健康?都过度了还利于的话就不叫过度了。

    让我们优化一下这个问题,改成:为什么 API 设计要在一开始设计的时候尽可能地多做优化? 是不是更好?

    回答当然 no,结合上下文语境,作者设计这个问题就是想要和答案反着来,修改后的问题没有这个效果。

    那要怎么问更好呢?。。。没完没了了!!!

    这就是完美主义,所以凡事一个度吧,我觉得这个才是最难,也是最有趣的。过度追求完美只会让自己更累,举步维艰,永无止境。

  37. Restful API算是一个HTTP的规范和标准了,你要说是最佳实践也好,总之,它是一个全世界对HTTP API的一个共识
    博主如果早出生10年一定是 SOAP 的拥护者并视 RESTful 为异端

    1. 所有接口都是 post 请求不是不遵循ESTful 规范, 而是选择了 rpcstyle, 这是 trade off, 看中的是 rpcstyle 描述接口可以像 function 一样被不同语言调用, 获得结果, 产生副作用. 不应该仅仅只是基于共识这种莫须有的理由而去选择 RESTful.

  38. 你好,能否请教下,本文提到的过滤仅仅是只针对搜索到的结果,在前端进行过滤?即过滤是不会发往后端?因为如果过滤也要发往后端处理的话,那搜索 和 过滤 有什么本质区别么?

  39. 本文的拆解,至少违反了陈叔自己在《我做系统架构的一些原则》中的两个原则:
    原则一:关注于真正的收益而不是技术本身
    原则八:不要迁就老旧系统的技术债务

    这些拆解分析更多的是为了遵守HTTP、REST API而去这样做,或者说是站在HTTP、REST API设计初衷的角度去解释为什么这样做更好。而不是去真正对比,POST一把梭是否真的好。

    陈叔以及大多数人的这种用法,也违反了“分层”的原则。HTTP只是基础协议,从OSI模型上可能它是7层,但其实真正的7层,是用户自己的业务逻辑(当然也可以叫做8层)。对于运维层面的监控,可以由代理、中间件那些去处理HTTP这一层本身的状态码比如500系列。对于业务层的监控,完全可以业务层自己去做分开的一套东西。

    HTTP协议年代早,没有历史经验可以借鉴,所以造成了HTTP协议本身就像一个婊子——会的花样姿势很多,但它并不是真爱。

    协议交互的本质,就是数据交互进而业务实现。对于协议交互本身,主要需要两个要素:
    1. 命令/路由
    2. body,数据载体
    POST一把梭、body自带业务层的各种错误码更简洁,HTTP状态码,上面也说过了,运维、代理、中间件层面去处理就足够了,分层清晰

    RPC或者像游戏、IM那些软件,可以Call、Broadcast或者单纯的通知不需要回复,都比HTTP能应对更全面的业务场景。虽然由于互联网积累了太多历史包袱、不能全面废除,但是对于用法,用POST一把梭,可以为以后整个互联网通讯简化带来更多好处,对于开发人员心智负担也是个减负。

    牛逼的人都是把复杂问题简单化,而不是因为读了更多书、学了更多知识点,就一定要去把这些知识点各种拿来用。
    HTTP vs POST一把梭,就像是五岳剑派各种花式套路 vs 独孤九剑。

    个中哲学,或许只有真正懂得大道至简少即是多的“懒人”才能理解吧

    1. 非常赞同。 做业务开发的时候是面向对象, 而使用restful来设计, 却是面向资源。 将面向资源强行转换成面向资源, 从一个概念更丰富的概念转换成一个简陋的概念,非常容易产生歧义, 这也是为什么不停有人讨论这个方法是用PUT 还是 POST, 这个方法是GET 还是 POST . 除非这种转换带来显而易见的好处,否则不应该盲目认为 restful 才是最优秀的。 另外本文完全没有说明restful的好处,无非是大厂都在用,属于诉诸权威。 以前做过一点点这种restful风格的sdk 应用程序开发, 简直是噩梦, 还是那种简简单单的设计更容易使用。

发表评论

您的电子邮箱地址不会被公开。