2019.7.11 补充一些关于缓存的内容

2020.03.25 补充参考资料

2021.07.20 补充参考资料,个人觉得写得很不错

2021.07.20
参考资料 6 写得很清晰,有兴趣的读者不妨一读。
2014 年以前的规范中要求 GET 请求如果有 body,则 body 必须被忽略(虽然不一定报错,但 body 会被忽略);现在没有这个限制了。现在不在 GET 中使用 body 的主要还是因为 GET body 是未定义行为,很有可能不被支持。


2020.03.25
参考MDN中对安全(Safe)的定义,“任何应用都不应让 GET 请求修改服务端的状态”。如果不需要修改状态,为什么要在GET请求里附带额外的body呢?用query就足够了呀。


故事还得从一个bug说起。今天有人问我,为什么发到后端的请求400了,我说肯定是参数不对,你去检查检查GET、POST之类的方法写没写对,要么就是字段没对上,无非是这几个问题。然后他说检查过了,没问题啊;我不太相信,但是看了看前端发送的请求,好像确实没啥问题:

java中get请求参数可以放body吗 get请求参数放在body_HTTP


我说既然这样,那肯定是后端写错了,但后端说他已经用postman测过了,肯定没问题。这就很有意思了。于是我要来了后端的代码:

java中get请求参数可以放body吗 get请求参数放在body_服务器_02

不出所料,后端把GET请求里的参数当成body的内容了,把@RequestBody改成@RequestParam应该就没问题了;改完之后果然好了。

这本来是一个很简单的问题,没什么可说的,但是他们接着问我,为什么GET请求里不能用body?我寻思着平时也没什么人在GET请求里加body吧,而且一直以来都听说这么用不好。但是为什么不好呢?所以我就查了一些资料,最后干脆又去RFC翻了翻。看到官方是这么说的:

[RFC7231] A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.

意思是你往GET请求里加body是不符合规范的,不保证所有的实现都支持(主要是以前的实现,因为以前曾经有相应的规定),要是出了啥问题别怪我没提醒你。而且据说老版本的postman是不支持在GET请求里加body的,也是最近才加上的支持;所以要放在以前也就没这些问题了,以前的postman根本发不了带body的GET请求。

但是这一条并不是强制规定。我看很多人都强调一点,GET请求不应携带请求体,服务器应忽略(或者说丢弃)GET请求的请求体。这一条的确是有依据的,来源如下:

[RFC2616] A server SHOULD read and forward a message-body on any request; if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.

当然,官方也只是说SHOULD,没有像前文一样措辞严厉地强调类似HEAD这种的请求MUST NOT have a message body:

[RFC2616] A message-body MUST NOT be included in a request if the specification of the request method does not allow sending an entity-body in requests.

但是很可惜的是,RFC2616已经过时了。现在的说法变成了这样,连SHOULD都直接去掉了,要求更加宽松:

[RFC7230] Request message framing is independent of method semantics, even if the method does not define any use for a message body.

难道是因为错的人多了,错的也变成了对的?不过即使是这样,也并不是在GET请求里加上body的理由。

这个问题算是解决了。但是我看到网上各路大神说到GET加body的时候,还提到一个,就是往GET里加body会导致缓存机制失效。“GET 被设计来用 URI 来识别资源,如果让它的请求体中携带数据,那么通常的缓存服务便失效了,URI 不能作为缓存的 Key。”我一开始以为是在服务器上配置的缓存,比如Nginx的cache之类的机制,后来发现好像并非如此。这个缓存,大概是指那种预加载和后存储,会涉及到一个网络请求的“安全性”。如果不安全,显然是不能缓存的。

那么,GET和POST的区别和应用?这问题挺复杂。简而言之,就是“安全”和“不安全”的区别。什么是安全?不用承担责任。什么是不安全?可能需要承担责任。举个例子,点击某个链接以同意某个协议,这个请求明显就是不安全的,因为需要承担责任。如果采用GET,就违反了GET应该用于安全请求的规范。因为浏览器可能在你不知情的情况下预加载这个页面(因为是“安全”的GET请求),这样相当于你在不知情的情况下同意了某个协议,这显然是我们不希望看到的。在契约式的设计里,违反契约的行为是会带来严重的后果的。浏览器按照契约预加载了安全的GET请求,但这本身是不安全的,带来的后果自然要由打破契约的人承担(将这个请求设计成GET的人出来挨打)。

之所以强调“安全”,而不是按照常规的说法强调副作用,因为有副作用的请求不代表不安全;举例来说,服务器有一个显示访问人数的功能,这个功能就可以用GET来做。虽然每次访问都会发送改变服务器状态(计数器)的请求,但用户不会因为这个请求承担责任,这个请求是安全的。至于什么GET请求的URL有长度限制(后来事实证明其实没有),什么GET请求的URL里不能有中文(或者说非ASCII吧),都只是实现上的区别;从最初的设计上来说区别并不在这里。

当然,这些都是纯粹的理论层面的东西。如果遵守RESTful的规范,采用语义化的GET/POST请求,自然也就不会有这些问题了。因为通常来说,查询是安全的;这也是GET的主要作用。

说起来也挺有意思的,学习了这么久,经常提起RFC,也没搞清楚RFC究竟是个啥玩意,这次就一并查了。虽然我总觉得这是受到6f名词解释的影响……原来是叫“Request For Comments”。


参考文章(不分先后):

  1. 不再以讹传讹,GET和POST的真正区别
  2. HTTP GET with request body
  3. 谁说 HTTP GET 就不能通过 Body 来发送数据呢?
  4. URIs, Addressability, and the use of HTTP GET and POST
  5. Safe(安全)
  6. HTTP GET with request body

此外,'URIs, Addressability, and the use of HTTP GET and POST’这篇文章我翻译成了中文,欢迎各位阅读并指正;翻译水平实在有限,只能说“尽最大努力交付”。