0%

[计网]http之GET和POST区别

浏览器的GET和POST#

这里特指浏览器中 非Ajax的HTTP请求,即从 HTML和浏览器诞生就一直使用的HTTP协议中的GET/POST。浏览器用 GET请求来获取一个html页面/图片/css/js等资源;用 POST来提交一个< form>表单,并 得到一个结果的网页

GET#

“读取“一个资源。比如Get到一个html文件。 反复读取不应该对访问的数据有副作用。比如”GET一下,用户就下单了,返回订单已受理“,这是不可接受的。没有副作用被称为 “幂等“(Idempotent)。因为 GET是读取,就可以 对GET请求的数据做缓存。这个缓存可以做到浏览器本身上(彻底 避免浏览器发请求),也可以做到代理上(如 nginx),或者 做到server端(用Etag,至少可以减少带宽消耗)

POST#

在页面里 < form> 标签会定义 一个表单。点击其中的 submit元素会发出一个 POST请求让服务器做一件事。这件事往往是 有副作用的,不幂等的。不幂等也就意味着 不能随意多次执行。因此也就 不能缓存。比如通过POST下一个单,服务器创建了新的订单,然后返回订单成功的界面。这个页面不能被缓存。试想一下,如果POST请求被浏览器缓存了,那么下单请求就可以不向服务器发请求,而直接返回本地缓存的“下单成功界面”,却又没有真的在服务器下单。那是一件多么滑稽的事情。因为POST可能有副作用,所以浏览器实现为 不能把POST请求保存为书签。想想,如果点一下书签就下一个单,是不是很恐怖?。此外如果尝试重新执行POST请求,浏览器也会弹一个框提示下这个刷新可能会有副作用,询问要不要继续。

改造GET和POST#

当然,服务器的 开发者完全可以把GET实现为有副作用;把POST实现为没有副作用。只不过这和浏览器的预期不符。把GET实现为有副作用是个很可怕的事情。 我依稀记得很久之前百度贴吧有一个因为 使用GET请求可以修改管理员的权限而造成的安全漏洞。反过来,把没有副作用的请求用POST实现,浏览器该弹框还是会弹框,对用户体验好处改善不大。但是后边可以看到,将HTTP POST作为接口的形式使用时,就没有这种弹框了。于是 把一个POST请求实现为幂等就有实际的意义POST幂等能让很多业务的前后端交互更顺畅,以及避免一些因为前端bug,触控失误等带来的重复提交。将一个有副作用的操作实现为幂等必须得从业务上能定义出怎么就算是“重复”。如提交数据中增加一个dedupKey在一个交易会话中有效,或者 利用提交的数据里可以天然当dedupKey的字段。这样万一用户强行重复提交,服务器端可以做一次防护。

GET和POST数据的格式区别#

GET和POST 携带数据的格式也有区别。 当浏览器发出一个GET请求时,就意味着 要么是用户自己在浏览器的地址栏输入,要不就是 点击了html里a标签的href中的url。所以其实 并不是GET只能用url,而是 浏览器直接发出的GET只能由一个url触发。GET上要在url之外带一些参数就 只能依靠url上附带querystring。请求参数和对应的值附加在URL后面,利用一个 "?"代表URL的结尾与请求参数的开始,多个 参数用 "&"连接

1
2
3
http://server/action?id=a&id=b 

https://server/action/?info\=''\&abc\=c6cebb78a7be\&server\=52300 (这里加了转义字符'\',待验证是否必要)

但是HTTP协议本身并没有这个限制。浏览器的POST请求都来自表单提交。每次提交,表单的数据被浏览器用编码到HTTP请求的body里。浏览器发出的POST请求的body主要有有两种格式,一种是 application/x-www-form-urlencoded用来传输简单的数据,大概就是"key1=value1&key2=value2"这样的格式。另外一种是 传文件,会采用multipart/form-data格式。采用后者是因为application/x-www-form-urlencoded的编码方式对于文件这种二进制的数据非常低效。浏览器在POST一个表单时,url上也可以带参数,只要< form action="url" >里的url带querystring就行。只不过表单里面的那些用< input> 等标签经过用户操作产生的数据都在会在body里。因此我们一般会泛泛的说“GET请求没有body,只有url,请求数据放在url的querystring中;POST请求的数据在body中“。但这种情况仅限于浏览器发请求的场景。

参考 四种常见的 POST 提交数据方式

接口中的GET和POST#

这里是指通过浏览器的Ajax api,或者iOS/Android的App的http client,java的commons-httpclient/okhttp或者是curl,postman之类的工具 发出来的GET和POST请求。此时GET/POST不光能用在 前端和后端的交互中,还能用在 后端各个子服务的调用中(即当一种 RPC协议使用)。尽管RPC有很多协议,比如thrift,grpc,但是http本身 已经有大量的现成的支持工具可以使用,并且对人类很友好,容易debug。HTTP协议在 微服务中的使用是相当普遍的。当用 HTTP实现接口发送请求时,就 没有浏览器中那么多限制了,只要是 符合HTTP格式的就可以发。HTTP请求的格式,大概是这样的一个字符串:

1
2
3
4
5
6
7
<METHOD> <URL> HTTP/1.1\r\n
<Header1>: <HeaderValue1>\r\n
<Header2>: <HeaderValue2>\r\n
...
<HeaderN>: <HeaderValueN>\r\n
\r\n
<Body Data....>
其中的“"可以 是 GET或POST,或者其他的HTTP Method,如 PUT、DELETE、OPTION……。HTTP是 基于TCP/IP的关于数据如何在万维网中如何通信的协议。HTTP的底层是TCP/IP。所以GET和POST的底层也是 TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样的。因此从协议本身看,并 没有什么限制说GET一定不能没有bodyPOST就一定不能把参放到的querystring上。因此其实 可以更加自由的去利用格式。比如Elastic Search的_search api就 用了带body的GET;也可以自己开发接口 让POST一半的参数放在url的querystring里另外一半放body里**;你甚至还可以让所有的参数都放Header里——可以做各种各样的定制,只要请求的客户端和服务器端能够约定好。

当然,太自由也带来了另一种麻烦,开发人员不得不每次讨论确定参数是 放url的path里,querystring里,body里,header里这种问题。于是就有了 一些列接口规范/风格。其中名气最大的当属 REST。REST充分运用 GET、POST、PUT和DELETE,约定了这4个接口分别获取、创建、替换和删除“资源”,REST最佳实践还推荐在 请求体使用json格式。这样仅仅通过看HTTP的method就可以明白接口是什么意思,并且解析格式也得到了统一。

GET和POST还有一个重大区别:#

GET产生一个TCP数据包;POST产生两个TCP数据包。

对于GET方式的请求,浏览器会把 http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器 先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。 注: 1. GET与POST都有自己的语义,不能随便混用。

  1. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。

  2. 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

json相对于x-www-form-urlencoded的优势:#

  1. 可以有 嵌套结构
  2. 可以支持 更丰富的数据类型。通过一些框架,json可以直接被服务器代码映射为业务实体。用起来十分方便。但是 如果是写一个接口支持上传文件,那么还是 multipart/form-data格式更合适

安全性#

我们常听到GET不如POST安全,因为 POST用body传输数据,而 GET用url传输,更加容易看到。但是 从攻击的角度,无论是GET还是POST都不够安全,因为 HTTP本身是明文协议。每个 HTTP请求和返回的每个byte都会在网络上明文传播,不管是url,header还是body。这完全不是一个“是否容易在浏览器地址栏上看到“的问题。为了避免传输中数据被窃取,必须做从客户端到服务器的端端加密。业界的通行做法就是 https——即 用SSL协议协商出的密钥加密明文的http数据。这个 加密的协议和HTTP协议本身相互独立。如果是利用HTTP开发公网的站点/App,要保证安全,https是最最基本的要求。当然,端端加密并不一定非得用https。比如国内金融领域都会用私有网络,也有GB的加密协议SM系列。但除了军队,金融等特殊机构之外,似乎并没有必要自己发明一套类似于ssl的协议。

回到HTTP本身,的确 GET请求的参数更倾向于放在url上,因此 有更多机会被泄漏。比如携带私密信息的url会展示在地址栏上,还可以分享给第三方,就非常不安全了。此外,从客户端到服务器端,有大量的中间节点,包括网关,代理等。他们的access log通常会输出完整的url,比如nginx的默认access log就是如此。如果url上携带敏感数据,就会被记录下来。但请注意,就算私密数据在body里,也是可以被记录下来的,因此如果请求要经过不信任的公网,避免泄密的唯一手段就是https。这里说的“避免access log泄漏“仅仅是指避免可信区域中的http代理的默认行为带来的安全隐患。

reference#

  1. 知乎--GET 和 POST 到底有什么区别?
  2. 都 2019 年了,还问 GET 和 POST 的区别
  3. 四种常见的 POST 提交数据方式