HTTP协议中GET和POST方法的区别已经是老生常谈了,也是面试热门话题,我对此只有一个粗浅的印象,今天腾讯一面又被
问到了,所以想写一篇文章好好整理一下。

通常的理解

w3cschools关于这个问题的解答比较 GET 与 POST列出了
一般的理解:

方法 GET POST
后退按钮/刷新 无害(客户端向服务器的资源发起的请求不会引起服务端的任何状态变化) 数据会被重新提交(浏览器应该告知用户数据会被重新提交)
书签 可收藏为书签 不可收藏为书签
缓存 能被缓存 不能缓存
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded或mutipart/form-data。为二进制数据使用多重编码。
历史 参数保留在浏览器历史中 参数不会保存在浏览器历史中
对数据长度的限制 是的。当发送数据时,GET方法向URL添加数据;URL的长度是受限制的(URL的最大长度是2048个字符) 无限制
对数据类型的限制 只允许ASCII字符 没有限制。也允许二进制数据
安全性 与POST相比,GET的安全性较差,因为所发送的数据是URL的一部分。在发送密码或其他敏感信息时绝不要使用GET! POST比GET更安全,因为参数不会被保存在浏览器历史或web服务器日志中
可见性 数据在URL中对所有人都是可见的。 数据不会显示在URL中。

后来经同学指出,这里关于URL的长度受限制这一点是不对的,HTTP协议并没有限制URL的长度,具体的长度是由浏览器和系统来约束的。

这个对比只是给出了一些现象上的区别,但并没有解释为什么,对于这个问题的理解不能就停在这一层。

理解错了?

有一篇文章99%的人都理解错了HTTP中GET与POST的区别否定了上述回答:”很遗憾,
这不是我们要的回答!”,作者说:

GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的guising和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
GET和POST还有一个重大区别,简单的说:GET产生一个TCP数据报:POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一起发送出去,服务器行用200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器仔发送

都讲到TCP了,感觉很高大上有木有,起码当时看到这篇文章的我是信了的。

因为POST需要两步,时间上消耗的更多一些,看起来GET比POST更高效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但是这是一个坑!跳入需谨慎。为什么?

  1. GET与POST都有自己的语意,不能随便混用。
  2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证包完整性上,有非常大的优点。
  3. 并不是所有的浏览器都会在POST中发送两次包,Firefox就只发送一次。

反转?

但是在逛知乎时有看到这篇文章”听说『99% 的人都理解错了 HTTP 中 GET 与 POST 的区别』??指出了前文中的两个错误:

100 continue 只有在请求里带了Expect:100-continue header的时候才有意义。

我们通常在讨论GET和POST的时候,实际上讨论的是specification,而不是implementation。什么是specification?说白了就是相关的RFC。implementation则是实现了specification中描述的代码/库/产品,比如crul,python的requests库,或者Chrome。
POST请求怎么发送,根本就不是这段RFC在讨论的事情。RFC中只说明了 100 continue 和 Expect header 的联系,比如你想在GET请求里带body,一样可以发送两个TCP Expect:100-continue并等待100continue,这是符合标准的。
也就是说XHR发送两个TCP packets是关于implementation的知识,而不是关于specification的知识。你不能说Chrome 在 Ajax POST 的时候会发两个TCP packets,GET只发送一个 是GET和POST的区别,正如你不能因为北京PM2.5经常爆表就说国家关于工业废弃排放的标准有问题。

说的似乎更有道理,而且也搬出了RFC,specification,implementation这些高端词汇,这下子我这个吃瓜群众再也坐不住了,决定亲自去研究一下。

RFC探秘

首先,什么是RFC呢?简单理解就是互联网的规范,我们通常说所的协议就是RFC的形式存在。其中RFC7231里设计到了几个HTTP方法。其中牵涉到了一个很重要的词语:semantic语义,那么什么语义呢?

一种语言是合法句子的结合。什么样的句子是合法的呢?可以从两方面来判断:语法和语义。语法是和文法结构有关,然而语义是和按照这个结构所组合的单词符号的意义有关。合理的语法结构并不表明语义是合法的。
例如我们常说:我上大学,这个句子是符合语法规则的,也是符合预期规则的。但是大学上我,虽然符合语法规则,但没有什么意义,所以说是不符合语义的。

对于HTTP请求来说,语法是请求相应的格式,比如请求第一行必须是方法名 URI 协议/版本这样的格式,具体内容可以参见之前写的《图解HTTP》读书笔记里面的内容,凡事符合这个格式的请求都是合法的。

语义则定义了这一类型的请求具有什么样的性质。比如GET的语义就是获取资源,POST的语义是处理资源,那么在具体实现这两个方法时,就必须考虑其语义,作出符合其语义的行为。

当然在符合语法前提下实现违背语义的行为也是可以做到的,比如使用GET方法修改用户信息,POST获取资源列表,这样就只能说这个请求是合法的,但不是符合语义的。

RFC7231里紧接着定义了HTTP方法的几个特性:

  1. Safe - 安全
    这里的安全和通常理解的安全意义不同,如果一个方法的语义在本质上是可读的,那么这个方法就是安全的。客户端向服务端的资源发起的请求如果使用了是安全的方法,就不应该引起任何的状态变化,因此也是无害的。此RFC定义,GET、HEAD、OPTIONS会让TRACE这几个方法是安全的。但是这个定义只是规范,并不能保证方法的实现也是安全的,服务器的实现可能会不符合方法语义,正如上文说过的使用GET修改用户信息的情况。
  2. Idempotent -幂等
    幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同。按照RFC规范,PUT、DELETE和安全方法都是幂等的。同样,这也仅仅是规范,服务端实现是否幂等是无法确保的。
    引入幂等主要是为了处理同一个请求重复发送的情况。比如在请求响应前失去连接。如果方法是幂等的,就可以放心的重发一次请求。这也是浏览器在后退/刷新遇到POST会给用户提示的原因:POST语义不是幂等的,重复请求可能会带来意想不到的后果。

  3. Cacheable - 可缓存性。
    顾名思义就是一个方法是否可以被缓存,此RFC里GET,HEAD和某些情况下的POST都是可以缓存的,但是绝大多数的浏览器的实现里仅仅支持GET和HEAD。关于缓存的更多内容可以去看RFC7234。

语义之争

走到这一步,其实就明白了要理解这两个方法的本质区别,本质上是语义的对比而不是语法的对比,是specification的对比而不是implementation的对比。

关于这两种方法的语义,RFC7231里原文已经写的很好了,翻译过来大概是:

  • GET的语义是请求获取特定的资源。GET方法是安全、幂等、可缓存的(除非有Cache-ControlHeader的约束),GET方法的报文主题没有任何语义。

  • POST的语义是根据请求负荷(报文主题)对指定的资源作出处理,具体的处理方法视资源类型而不同。POST不安全、不幂等,(大部分实现)不可缓存。为了针对其不可缓存性,有一系列方法来进行优化,这里不详细阐述。

还是举一个通俗的栗子吧,在微博这个场景里,GET的语义会被用在看看我的Timeline上最新的20条微博这样的场景,而POST的语义会被用在发微博、评论、点赞这样的场景中。

转自: