GET和POST

 

w3school给出的比较

下面的表格比较了两种 HTTP 方法:GET 和 POST。

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

 

 

从HTTP自己来说GET与POST的区别

征求意见稿(英语:Request For Comments,缩写为RFC),是由互联网工程任务组(IETF)发布的一系列备忘录。

RFC7231里定义了HTTP方法的几个性质:

  1. Safe - 安全 这里的「安全」和一般理解的「安全」意义不一样,若是一个方法的语义在本质上是「只读」的,那么这个方法就是安全的。客户端向服务端的资源发起的请求若是使用了是安全的方法,就不该该引发服务端任何的状态变化,所以也是无害的。 此RFC定义,GET, HEAD, OPTIONS 和 TRACE 这几个方法是安全的。 可是这个定义只是规范,并不能保证方法的实现也是安全的,服务端的实现可能会不符合方法语义,正如上文说过的使用GET修改用户信息的状况。 引入安全这个概念的目的是为了方便网络爬虫和缓存,以避免调用或者缓存某些不安全方法时引发某些意外的后果。User Agent(浏览器)应该在执行安全和不安全方法时作出区分对待,并给用户以提示。

  2. Idempotent - 幂等 幂等的概念是指同一个请求方法执行屡次和仅执行一次的效果彻底相同。按照RFC规范,PUT,DELETE和安全方法都是幂等的。一样,这也仅仅是规范,服务端实现是否幂等是没法确保的。 引入幂等主要是为了处理同一个请求重复发送的状况,好比在请求响应前失去链接,若是方法是幂等的,就能够放心地重发一次请求。这也是浏览器在后退/刷新时遇到POST会给用户提示的缘由:POST语义不是幂等的,重复请求可能会带来意想不到的后果。

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

 

语法与语义

对语法和语义的理解。语法是和文法结构有关,然而语义是和按照这个结构所组合的单词符号的意义有关。合理的语法结构并不代表语义是合法的。例如咱们常说:我上大学,这个句子是符合语法规则的,也符合语义规则。可是大学上我,虽然符合语法规则,但没有什么意义,因此说是不符合语义的。

HTTP请求报文的格式:

从语法上来讲,只要符合该格式的请求就是符合语法的。语义上来讲,GET的语义就是「获取资源」,POST的语义是「处理资源」,那么在具体实现这两个方法时,就必须考虑其语义,作出符合其语义的行为,若是使用GET方法建立用户信息,POST获取资源列表,这样就只能说这个请求是「合法」的,但不是「符合语义」的。

 

浏览器的GET和POST

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

GET

“读取“一个资源。好比Get到一个html文件。反复读取不该该对访问的数据有反作用。好比”GET一下,用户就下单了,返回订单已受理“,这是不可接受的。没有反作用被称为“幂等“。

POST

在页面里<form> 标签会定义一个表单。点击其中的submit元素会发出一个POST请求让服务器作一件事。这件事每每是有反作用的,不幂等的。不幂等也就意味着不能随意屡次执行。所以也就不能缓存,不能保存书签等。

 

GET和POST携带数据的格式也有区别。当浏览器发出一个GET请求时,就意味着要么是用户本身在浏览器的地址栏输入,要不就是点击了html里a标签的href中的url。因此其实并非GET只能用url,而是浏览器直接发出的GET只能由一个url触发。因此没办法,GET上要在url以外带一些参数就只能依靠url上附带querystring。可是HTTP协议自己并无这个限制。

浏览器的POST请求都来自表单提交。每次提交,表单的数据被浏览器用编码到HTTP请求的body里。浏览器发出的POST请求的body主要有有两种格式,一种是application/x-www-form-urlencoded用来传输简单的数据,大概就是"key1=value1&key2=value2"这样的格式。另一种是传文件,会采用multipart/form-data格式。采用后者是由于application/x-www-form-urlencoded的编码方式对于文件这种二进制的数据很是低效。

 

接口中的GET和POST

这里是指经过浏览器的Ajax api,或者iOS/Android的App的http client,java的commons-httpclient/okhttp或者是curl,postman之类的工具发出来的GET和POST请求。

当用HTTP实现接口发送请求时,就没有浏览器中那么多限制了,只要是符合HTTP格式的就能够发。其中的「请求方法」能够是GET也能够是POST,或者其余的HTTP Method,如PUT、DELETE、OPTION……。从协议自己看,并无什么限制说GET必定不能没有body,POST就必定不能把参放到<URL>的querystring上。所以其实能够更加自由的去利用格式。好比Elastic Search的_search api就用了带body的GET;也能够本身开发接口让POST一半的参数放在url的querystring里,另一半放body里;你甚至还可让全部的参数都放Header里——能够作各类各样的定制,只要请求的客户端和服务器端可以约定好。

固然,太自由也带来了另外一种麻烦,因而就有了一些列接口规范/风格。其中名气最大的当属REST。REST充分运用GET、POST、PUT和DELETE,约定了这4个接口分别获取、建立、替换和删除“资源”,REST最佳实践还推荐在请求体使用json格式。这样仅仅经过看HTTP的method就能够明白接口是什么意思,而且解析格式也获得了统一。

json相对于x-www-form-urlencoded的优点在于1)能够有嵌套结构;以及 2)能够支持更丰富的数据类型。经过一些框架,json能够直接被服务器代码映射为业务实体。用起来十分方便。可是若是是写一个接口支持上传文件,那么仍是multipart/form-data格式更合适。

 

REST接口规范

在REST中, 【GET】 + 【资源定位符】被专用于获取资源或者资源列表,以下。与浏览器的场景相似,REST GET也不该该有反作用,因而能够被反复无脑调用。

 GET http://foo.com/books          获取书籍列表
 GET http://foo.com/books/:bookId 根据bookId获取一本具体的书

REST 【POST】+ 【资源定位符】则用于“建立一个资源”,以下。这里就能留意到浏览器中用来实现表单提交的POST,和REST里实现建立资源的POST语义上的不一样。

 POST http://foo.com/books
 {
  "title": "西游记",
  "author": "施耐庵",
  ...
 }

REST POST和REST PUT的区别有些api是使用PUT做为建立资源的Method。PUT与POST的区别在于,PUT的实际语义是“replace”replace。REST规范里提到PUT的请求体应该是完整的资源,包括id在内。好比上面的建立一本书的api也能够定义为:

 PUT http://foo.com/books
 {
 "id": "BOOK:affe001bbe0556a",
 "title": "西游记",
 "author": "施耐庵",
 ...
 }

服务器应该先根据请求提供的id进行查找,若是存在一个对应id的元素,就用请求中的数据总体替换已经存在的资源;若是没有,就用“把这个id对应的资源从【空】替换为【请求数据】“。直观看起来就是“建立”了。与PUT相比,POST更像是一个“factory”,经过一组必要的数据建立出完整的资源。

 

 

 

关于安全性

咱们常听到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代理的默认行为带来的安全隐患。好比你是不太但愿让本身公司的运维同窗从公司主网关的log里看到用户的密码吧。

另外,上面讲过,若是是用做接口,GET实际上也能够带body,POST也能够在url上携带数据。因此实际上到底怎么传输私密数据,要看具体场景具体分析。固然,绝大多数场景,用POST + body里写私密数据是合理的选择。一个典型的例子就是“登陆”:

 POST http://foo.com/user/login
 {
  "username": "dakuankuan",
  "passowrd": "12345678"
 }

安全是一个巨大的主题,有由不少细节组成的一个完备体系,好比返回私密数据的mask,XSS,CSRF,跨域安全,前端加密,钓鱼,salt,…… POST和GET在安全这件事上仅仅是个小角色。所以单独讨论POST和GET自己哪一个更安全意义并非太大。只要记得通常状况下,私密数据传输用POST + body就好。

关于编码

常见的说法有,好比GET的参数只能支持ASCII,而POST能支持任意binary,包括中文。但其实从上面能够看到,GET和POST实际上都能用url和body。所以所谓编码确切地说应该是http中url用什么编码,body用什么编码。

先说下url。url只能支持ASCII的说法源自于RFC1738

Thus, only alphanumerics, the special characters "$-_.+!*'(),", and reserved characters used for their reserved purposes may be used unencoded within a URL.

实际上这里规定的仅仅是一个ASCII的子集[a-zA-Z0-9$-_.+!*'(),]。它们是能够“不经编码”在url中使用。好比尽管空格也是ASCII字符,可是不能直接用在url里。

那这个“编码”是什么呢?若是有了特殊符号和中文怎么办呢?一种叫作percent encoding的编码方法就是干这个用的:https://en.wikipedia.org/wiki/Percent-encodingen.wikipedia.org

这也就是为啥咱们偶尔看到url里有一坨%和16位数字组成的序列。

使用Percent Encoding,即便是binary data,也是能够经过编码后放在URL上的。

但要特别注意,这个编码方式只管把字符转换成URL可用字符,可是却无论字符集编码(好比中文究竟是用UTF8仍是GBK)这块早期一直都至关乱,也没有什么统一规范。好比有时跟网页编码同样,有的是操做系统的编码同样。最要命的是浏览器的地址栏是不受开发者控制的。这样,对于一样一个带中文的url,若是有的浏览器必定要用GBK(好比老的IE8),有的必定要用UTF8(好比chrome)。后端就可能认不出来。对此经常使用的办法是避免让用户输入这种带中文的url。若是有这种形式的请求,都改为用户界面上输入,而后经过Ajax发出的办法。Ajax发出的编码形式开发者是能够100%控制的。

不过目前基本上utf8已经大一统了。如今的开发者除非是被国家规定要求必定要用GB系列编码的场景,基本上不会再遇到这类问题了。

关于url的编码,阮一峰的一篇文章有比较详细的解释:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

顺便说一句,尽管在浏览器地址栏能够看到中文。但这种url在发送请求过程当中,浏览器会把中文用字符编码+Percent Encode翻译为真正的url,再发给服务器。浏览器地址栏里的中文只是想让用户体验好些而已。

再讨论下Body。HTTP Body相对好些,由于有个Content-Type来比较明确的定义。好比:

 POST xxxxxx HTTP/1.1
 ...
 Content-Type: application/x-www-form-urlencoded ; charset=UTF-8

这里Content-Type会同时定义请求body的格式(application/x-www-form-urlencoded)和字符编码(UTF-8)。

因此body和url均可以提交中文数据给后端,可是POST的规范好一些,相对不容易出错,容易让开发者安心。对于GET+url的状况,只要不涉及到在老旧浏览器的地址栏输入url,也不会有什么太大的问题。

回到POST,浏览器直接发出的POST请求就是表单提交,而表单提交只有application/x-www-form-urlencoded针对简单的key-value场景;和multipart/form-data,针对只有文件提交,或者同时有文件和key-value的混合提交表单的场景。

若是是Ajax或者其余HTTP Client发出去的POST请求,其body格式就很是自由了,经常使用的有json,xml,文本,csv……甚至是你本身发明的格式。只要先后端能约定好便可。

浏览器的POST须要发两个请求吗?

上文中的"HTTP 格式“清楚的显示了HTTP请求能够被大体分为“请求头”和“请求体”两个部分。使用HTTP时你们会有一个约定,即全部的“控制类”信息应该放在请求头中,具体的数据放在请求体里“。因而服务器端在解析时,老是会先彻底解析所有的请求头部。这样,服务器端老是但愿可以了解请求的控制信息后,就能决定这个请求怎么进一步处理,是拒绝,仍是根据content-type去调用相应的解析器处理数据,或者直接用zero copy转发。

好比在用Java写服务时,请求处理代码老是能从HttpSerlvetRequest里getParameter/Header/url。这些信息都是请求头里的,框架直接就解析了。而对于请求体,只提供了一个inputstream,若是开发人员以为应该进一步处理,就本身去读取和解析请求体。这就能体现出服务器端对请求头和请求体的不一样处理方式。

举个实际的例子,好比写一个上传文件的服务,请求url中包含了文件名称,请求体中是个尺寸为几百兆的压缩二进制流。服务器端接收到请求后,就能够先拿到请求头部,查看用户是否是有权限上传,文件名是否是符合规范等。若是不符合,就再也不处理请求体的数据了,直接丢弃。而不用等到整个请求都处理完了再拒绝。

为了进一步优化,客户端能够利用HTTP的Continued协议来这样作:客户端老是先发送全部请求头给服务器,让服务器校验。若是经过了,服务器回复“100 - Continue”,客户端再把剩下的数据发给服务器。若是请求被拒了,服务器就回复个400之类的错误,这个交互就终止了。这样,就能够避免浪费带宽传请求体。可是代价就是会多一次Round Trip。若是恰好请求体的数据也很少,那么一次性所有发给服务器可能反而更好。

基于此,客户端就能作一些优化,好比内部设定一次POST的数据超过1KB就先只发“请求头”,不然就一次性全发。客户端甚至还能够作一些Adaptive的策略,统计发送成功率,若是成功率很高,就老是所有发等等。不一样浏览器,不一样的客户端(curl,postman)能够有各自的不一样的方案。无论怎样作,优化目的老是在提升数据吞吐和下降带宽浪费上作一个折衷。

所以究竟是发一次仍是发N次,客户端能够很灵活的决定。由于无论怎么发都是符合HTTP协议的,所以咱们应该视为这种优化是一种实现细节,而不用扯到GET和POST自己的区别上。更不要当个什么世纪大发现。

到底什么算请求体

看完了上面的内容后,读者也许会对“什么是请求体”感到困惑不已,好比x-www-form-endocded编码的body算不算“请求体”呢?

从HTTP协议的角度,“请求头”就是Method + URL(含querystring)+ Headers;再后边的都是请求体。

可是从业务角度,若是你把一次请求当即为一个调用的话。好比上面的

 POST http://foo.com/books
 {
  "title": "西游记",
  "author": "施耐庵",
  ...
 }

那么这一行函数名和两个参数均可以看做是一个请求,不区分头和体。即使用HTTP协议实现,title和author编码到了HTTP请求体中。Java的HttpServletRequest支持用getParameter方法获取x-www-url-form-encoded中的数据,表达的意思就是“请求“的”参数“。

对于HTTP,须要区分【头】和【体】,Http Request和Http Response都这么区分。Http这么干主要用做

  • 对于HTTP代理

    • 支持转发规则,好比nginx先要解析请求头,拿到URL和Header才能决定怎么作(转发proxy_pass,重定向redirect,rewrite后从新判断……)

    • 须要用请求头的信息记录log。尽管请求体里的数据也能够记录,但通常只记录请求头的部分数据。

    • 若是代理规则不涉及到请求体,那么请求体就能够不用从内核态的page cache复制一份到用户态了,能够直接zero copy转发。这对于上传文件的场景极为有效。

    • ……

  • 对于HTTP服务器

    • 能够经过请求头进行ACL控制,好比看看Athorization头里的数据是否能让认证经过

    • 能够作一些拦截,好比看到Content-Length里的数太大,或者Content-Type本身不支持,或者Accept要求的格式本身没法处理,就直接返回失败了。

    • 若是body的数据很大,利用Stream API,能够方便支持一块一块的处理数据,而不是一次性所有读取出来再操做,以致于占用大量内存。

    • ……

但从高一级的业务角度,咱们在乎的实际上是【请求】和【返回】。当咱们在说“请求头”这三个字时,也许实际的意思是【请求】。而用HTTP实现【请求】时,可能仅仅用到【HTTP的请求头】(好比大部分GET请求),也多是【HTTP请求头】+【HTTP请求体】(好比用POST实现一次下单)。

总之,这里有两层,不要混哦。

关于URL的长度

由于上面提到了不管是GET和POST均可以使用URL传递数据,因此咱们常说的“GET数据有长度限制“实际上是指”URL的长度限制“。

HTTP协议自己对URL长度并无作任何规定。实际的限制是由客户端/浏览器以及服务器端决定的。

先说浏览器。不一样浏览器不太同样。好比咱们常说的2048个字符的限制,实际上是IE8的限制。而且原始文档的说的实际上是“URL的最大长度是2083个字符,path的部分最长是2048个字符“。见https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer。IE8以后的IE URL限制我没有查到明确的文档,但有些资料称IE 11的地址栏只能输入法2047个字符,可是容许用户点击html里的超长URL。我没实验,哪位有兴趣能够试试。

Chrome的URL限制是2MB,见https://chromium.googlesource.com/chromium/src/+/master/docs/security/url_display_guidelines/url_display_guidelines.md

Safari,Firefox等浏览器也有本身的限制,但都比IE大的多,这里就不挨个列出了。

然而新的IE已经开始使用Chrome的内核了,也就意味着“浏览器端URL的长度限制为2048字符”这种说法会慢慢成为历史。

其余的客户端,好比Java的,js的http client大多数也并无限制URL最大有多长。

除了浏览器,服务器这边也有限制,好比apache的LimieRequestLine指令。

apache实际上限制的是HTTP请求第一行“Request Line“的长度,即<METHOD><URL> <VERSION>那一行。

再好比nginx用large_client_header_buffers 指令来分配请求头中的很长数据的buffer。这个buffer能够用来处理url,header value等。

Tomcat的限制是web.xml里maxHttpHeaderSize来设置的,控制的是整个“请求头”的总长度。

为啥要限制呢?若是写过解析一段字符串的代码就能明白,解析的时候要分配内存。对于一个字节流的解析,必须分配buffer来保存全部要存储的数据。而URL这种东西必须看成一个总体看待,没法一块一块处理,因而就处理一个请求时必须分配一整块足够大的内存。若是URL太长,而并发又很高,就容易挤爆服务器的内存;同时,超长URL的好处并很少,我也只有处理老系统的URL时由于不敢碰原来的逻辑,又得追加更多数据,才会使用超长URL。

对于开发者来讲,使用超长的URL彻底是给本身埋坑,须要同时要考虑先后端,以及中间代理每个环节的配置。此外,超长URL会影响搜索引擎的爬虫,有些爬虫甚至没法处理超过2000个字节的URL。这也就意味着这些URL没法被搜到,坑爹啊。

其实并无太大必要弄清楚精确的URL最大长度限制。我我的的经验是,只要某个要开发的资源/api的URL长度有可能达到2000个bytes以上,就必须使用body来传输数据,除非有特殊状况。至于究竟是GET + body仍是POST + body能够看状况决定。

留意,1个汉字字符通过UTF8编码 + percent encoding后会变成9个字节,别算错哦。

 

 

参考连接:

https://www.zhihu.com/question/28586791/answer/145424285

https://www.zhihu.com/question/28586791/answer/767316172

https://www.w3school.com.cn/tags/html_ref_httpmethods.asp

相关文章
相关标签/搜索