HTTP新QUERY方法详解:为何需要及如何应用
1. 引言
在RESTful API的世界里,我们长期遵循一套严格的(自我施加的)规则。无论你是用GET获取数据、用POST创建实体,还是用PUT更新资源,HTTP方法都会告诉服务器你的意图。最近,RFC 10008发布,定义了新的HTTP QUERY方法。既然我们已经有了其他HTTP方法,为什么还需要这个新方法?让我们一探究竟。从纯技术角度看,HTTP方法只是一个字符串。理论上,你可以用FETCH /api/v1/users代替GET /api/v1/users。但在实践中,围绕GET和POST等知名HTTP方法,存在大量RFC和隐式的、未文档化的行为。例如,当你输入地址或点击书签时,浏览器会发送GET请求。标准HTTP表单只允许GET和POST作为方法。大多数代理、防火墙和Web服务器只允许标准HTTP方法。那么,既然我们已经有一套运行了几十年的现有方法,为什么还要引入新的HTTP方法?
2. 使用GET进行查询
传统上,如果你想过滤资源,你会在GET请求中使用查询参数(例如,/api/v1/users?role=admin status=active sort=desc)。这对于简单过滤器效果很好。然而,当你需要执行复杂的关系查询、深层嵌套或高级逻辑时,URL会变得庞大、难以阅读,有时还会触及浏览器或服务器的字符限制。其他潜在问题包括:将非ASCII或特殊字符作为参数发送需要编码,这会增加请求大小;服务器和其他中间件可能会记录请求参数,这在某些情况下可能有问题;表达某些数据结构(如数组)的方式不明确且依赖于实现(例如,?roles[0]=admin roles[1]=reporter vs ?roles=admin roles=reporter vs ?roles[]=admin roles[]=reporter);表达深层嵌套结构也是如此。既然这些都是将数据作为查询参数发送的缺点,为什么不直接发送带有JSON请求体的GET请求呢?同样,从理论角度看,这应该可行。没有任何HTTP RFC明确禁止在执行HTTP GET请求时使用请求体,但表明不应这样做。因此,各种客户端、代理和Web服务器实现处理带有请求体的GET请求方式不同。有些直接拒绝,有些直接丢弃请求体,而有些则解释它。因此,使用带有请求体的HTTP GET是一个坏主意,因为例如,位于企业防火墙后面或使用不同浏览器的用户可能无法使用你的网站。这也是为什么没有新的RFC指定GET请求现在应该支持请求体的原因,因为那会破坏许多现有实现。
3. 变通方法:使用POST进行查询
由于使用GET发送请求体可能引入问题,变通方法是使用POST。虽然POST允许请求体,但它引入了显著的语义问题。POST被定义为非幂等的,旨在用于资源创建或处理。虽然这听起来可能不是一个大问题,但在实现例如失败时自动重试时可能会很烦人。由于GET方法被定义为安全且幂等的,只要服务器实现正确,我们可以重试失败的请求而不用担心副作用。这也使得代理或其他中间件无法自动理解操作是只读的。例如,中间件可能会自动缓存GET请求一段时间,这对POST请求不起作用。
4. QUERY方法
上述所有原因导致在经过多年讨论后,QUERY方法被指定。QUERY方法没什么特别,RFC大致说明它类似于GET方法,但带有请求体。它旨在安全且幂等。QUERY请求可以被缓存,但实现必须小心地将请求内容纳入缓存键。总之,它最终为复杂的搜索查询提供了一个合适的HTTP方法。
5. QUERY的注意事项
可能很想立即将所有与搜索相关的端点切换到使用QUERY。在此之前,你需要考虑几件事。对HTTP QUERY的支持仍然非常有限,并且可能持续一段时间。它可能需要数年才能在所有地方得到完全支持。例如,Kreya在最近的1.20版本中增加了对HTTP QUERY的开箱即用支持(尽管之前已经可以发送自定义HTTP方法)。其他客户端、代理和Web服务器可能仍然拒绝它。URL参数中包含数据的标准GET查询仍然完全没问题。如果没有立即需要将这些改为QUERY方法,就保持原样。如果你的用户应该能够共享或书签过滤数据的链接,继续使用GET请求。共享QUERY请求的链接不起作用。为QUERY请求实现自定义缓存比GET请求更困难,因为你需要考虑请求体。
6. 结论
简而言之,HTTP QUERY取代了POST用于只读请求。它可能需要一段时间才能在所有地方得到完全支持,但如果标准GET请求不能满足你的用例,你仍然应该考虑(并测试!)它。
🔗 原文链接:https://kreya.app/blog/new-http-query-method-explained/