结合知乎用户@杨光的见解,整理本篇文章。
作者:杨光
链接:https://www.zhihu.com/question/28586791/answer/145424285
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我们通常在讨论GET vs POST的时候,实际上讨论的是specification(详述),而不是implementation(实现)。什么是 specification?说白了就是相关的RFC。implementation则是所有实现了specification中描述的代码/库/产品,比如curl,Python 的requests库,或者Chrome。
POST请求怎么发送,根本就不是这段RFC在讨论的事情。RFC 中只说明了 100 continue 和 Expect header 的联系,比如你想在 GET 请求里带 body,一样可以发送 Expect: 100-continue 并等待 100 continue,这是符合标准的。
也就是说,『XHR发送两个TCP packets』是关于implementation的知识,而不是关于specification的知识。你不能说『Chrome 在 AJAX POST 的时候会发两个TCP packets,GET 只会发一个』是GET和POST的区别,正如你不能因为北京PM 2.5 经常爆表就说国家关于工业废气排放的标准有问题。
说得似乎更有道理,而且也搬出了RFC,specification,implementation这些高端词汇,这下子我这个吃瓜群众再也坐不住了,决定亲自去研究一下。
RFC探秘
首先,什么是RFC呢?Wiki上面的定义是:
征求意见稿(英语:Request For Comments,缩写为RFC),是由互联网工程任务组(IETF)发布的一系列备忘录。文件收集了有关互联网相关信息,以及UNIX和互联网社区的软件文件,以编号排定。目前RFC文件是由互联网协会(ISOC)赞助发行。
简单理解RFC就是互联网的规范,我们通常所说的「协议」就是以RFC的形式存在,而现行的HTTP/1.1规范的RFC有如下几个:RFC7230, RFC7231, RFC7232, RFC7233, RFC7234,RFC7235。 其中RFC7231里的Section 4. Request Methods涉及到了几个HTTP方法,接下来仔细阅读这一章节。
The request method token is the primary source of request semantics; it indicates the purpose for which the client has made this request and what is expected by the client as a successful result.
这里牵涉到一个很重要的词语:semantic 「语义」,那么什么是语义呢?这一篇文章给出了解释:语法和语义的区别。
符号学家曾经如此来描述“语法学”和“语义学”,说前者是研究符合之间的关系;后者是研究符号与其指称之间的关系;(另外一个所谓的符号学分支是语用学,研究的是符号与其使用者之间的关系)
事实上,对计算机而言,所有的研究都局限在符号与符号之间的关系范围内。
从上述意义上讲,所谓的“语义学”跟“语法学”没有任何本质的区别。二者只不过是在不同的概括(抽象)程度上描述符号与符号之间的关系,包括能否组合,以及以什么样的方式(关系)组合等等。
譬如:关于“学校”这个词(符号单元)的语法描述是:它是一个名词(n),它能出现在动词的前面跟该动词组合成一种可称之为“主谓关系”的更大一些的符号串。它还能出现在动词的后面跟该动词组合成一种可称之为“述宾关系”的更大一些的符号串。诸如此类。
关于“学校”这个词的语义描述是:它可能是一个场所(location),或是一个集体(organization),它能出现在一些表动作行为意义的词语前面并跟该词语构成一种可称之为“施事——动作”关系的“语义结构”,它还能出现在动词的后面跟该动词构成一种可称之为“动作——受事”关系的“语义结构”。诸如此类。
人们通常把“名词”、“主语”这样的范畴称为“语法范畴”,而把“场所”、“施事”这样的范畴称为“语义范畴”。
实际上,它们并没有多大的差别。放在不同的层面和放在同一个层面上看,除了可以导致不同的心理感受外,并不表现出多大的效果差异。
一种语言是合法句子的集合。什么样的句子是合法的呢?可以从两方面来判断:语法和语义。语法是和文法结构有关,然而语义是和按照这个结构所组合的单词符号的意义有关。合理的语法结构并不表明语义是合法的。
例如我们常说:我上大学,这个句子是符合语法规则的,也符合语义规则。但是大学上我,虽然符合语法规则,但没有什么意义,所以说是不符合语义的。
对于HTTP请求来说,语法是指请求响应的格式,比如请求第一行必须是方法名URI协议/版本这样的格式,具体内容可以参见之前写的《图解HTTP》读书笔记里面的内容,凡是符合这个格式的请求都是合法的。
语义则定义了这一类型的请求具有什么样的性质。比如GET的语义就是「获取资源」,POST的语义是「处理资源」,那么在具体实现这两个方法时,就必须考虑其语义,做出符合其语义的行为。
当然在符合语法的前提下实现违背语义的行为也是可以做到的,比如使用GET方法修改用户信息,POST获取资源列表,这样就只能说这个请求是「合法」的,但不是「符合语义」的。 写到这里突然联想到XML里面的两个概念:Well Formed和Valid,似乎也正是语法和语义的理念呢。
XML标准中明确规定了XML文件应当遵守的规则,大致上分成基本规则和DTD(Document Type Definition)规定的XML文件结构规则。 一份XML文档也可以没有DTD。当一份XML文档不考虑DTD的情况下,符合其他对XML文件要求的规则时,该份XML文档就称为一份Well-Formed XML文档。
如果一个XML文档不是Well-Formed的,Browser就无法正常显示。
主要规则如下:
1.文档的开始必须是XML声明
2.含有数据的元素必须有起始标记和结束标记。
3.不含数据并且仅使用一个标记的元素必须以/>结束。
4.文档只能包含一个能够包含全部其他元素的元素,这个元素称为根元素
5.元素只能嵌套不能重叠<注意:是针对Well-Formed文档>
6.属性值必须加引号。(“”)
7.字符<和&只能用于起始标记和实体引用。
8.出现的实体引用只有&,<,>;,",'。<注意:是针对Well-Formed文档>
附注:
与之区别的是XML的Vaild文档。
Vaild文档:(基本规则+DTD)
Well-Formed文档:(基本规则)
来源:http://blog.csdn.net/qq_34992930/article/details/58712422
上文说到方法是请求语义的主要来源,也即是还有次要来源,一些请求Header可以进一步修饰请求的语义,比如一个带上了 Range Header的GET请求就变成了部分请求。
RFC7231里定义了HTTP方法的几个性质:
- Safe - 安全
这里的「安全」和通常理解的「安全」意义不同,如果一个方法的语义在本质上是「只读」的,那么这个方法就是安全的。客户端向服务端的资源发起的请求如果使用了是安全的方法,就不应该引起服务端任何的状态变化,因此也是无害的。 此RFC定义,GET, HEAD, OPTIONS 和 TRACE 这几个方法是安全的。
但是这个定义只是规范,并不能保证方法的实现也是安全的,服务端的实现可能会不符合方法语义,正如上文说过的使用GET修改用户信息的情况。
引入安全这个概念的目的是为了方便网络爬虫和缓存,以免调用或者缓存某些不安全方法时引起某些意外的后果。User Agent(浏览器)应该在执行安全和不安全方法时做出区分对待,并给用户以提示。 - Idempotent - 幂等
幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同。按照RFC规范,PUT,DELETE和安全方法都是幂等的。同样,这也仅仅是规范,服务端实现是否幂等是无法确保的。
引入幂等主要是为了处理同一个请求重复发送的情况,比如在请求响应前失去连接,如果方法是幂等的,就可以放心地重发一次请求。这也是浏览器在后退/刷新时遇到POST会给用户提示的原因:POST语义不是幂等的,重复请求可能会带来意想不到的后果。 - Cacheable - 可缓存性 顾名思义就是一个方法是否可以被缓存,此RFC里GET,HEAD和某些情况下的POST都是可缓存的,但是绝大多数的浏览器的实现里仅仅支持GET和HEAD。关于缓存的更多内容可以去看RFC7234。
在这三个特性里一直在强调同一个事情,那就是协议不等于实现:协议规定安全在实现里不一定安全,协议规定幂等在实现里不一定幂等,协议规定可缓存在实现里不一定可缓存。这其实就是上面那个作者提到的specification和implementation的关系。
语义之争
走到这一步,其实就明白了要理解这两个方法的区别,本质上是 「语义」的对比而不是「语法」的对比,是「Specification」的对比而不是「Implementation」的对比 。
关于这两种方法的语义,RFC7231里原文已经写得很好了:
The GET method requests transfer of a current selected representation for the target resource. GET is the primary mechanism of information retrieval and the focus of almost all performance optimizations. Hence, when people speak of retrieving some identifiable information via HTTP, they are generally referring to making a GET request.
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.
The POST method requests that the target resource process the representation enclosed in the request according to the resource’s own specific semantics.
勉强渣翻一下,再加上点自己的理解:
GET的语义是请求获取指定的资源。GET方法是安全、幂等、可缓存的(除非有 Cache-ControlHeader的约束),GET方法的报文主体没有任何语义。
POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST不安全,不幂等,(大部分实现)不可缓存。为了针对其不可缓存性,有一系列的方法来进行优化,以后有机会再研究(FLAG已经立起)。
还是举一个通俗栗子吧,在微博这个场景里,GET的语义会被用在「看看我的Timeline上最新的20条微博」这样的场景,而POST的语义会被用在「发微博、评论、点赞」这样的场景中。