GET 和 POST 到底有什么区别

整理自:https://www.zhihu.com/question/28586791/answer/767316172

比较一般的理解(现象上)

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

实质上,GET和POST都是HTTP中请求数据的方法。GET的语义是请求获取指定的资源。GET方法是安全、幂等、可缓存的(除非有 Cache-ControlHeader的约束),GET方法的报文主体没有任何语义。POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST不安全,不幂等,(大部分实现)不可缓存。

下面从两个角度对GET和POST进行辨析

浏览器的角度

指非AJAX的HTTP请求!
自古以来,浏览器就是用HTTP协议中的GET和POST进行通信。浏览器用GET获取html页面、图片、css、js等资源;用POST提交一个<form>表单,得到结果网页。
浏览器把GET和POST分别定义为:

GET

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

因为GET因为是读取,就可以对GET请求的数据做缓存。这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),或者做到server端(用Etag,至少可以减少带宽消耗)

POST

在页面里<form>标签会定义一个表单。点击其中的submit元素会发出一个POST请求让服务器做一件事。

这件事往往是有副作用的,不幂等的。不幂等也就意味着不能随意多次执行。因此也就不能缓存。比如通过POST下一个单,服务器创建了新的订单,然后返回订单成功的界面。这个页面不能被缓存。试想一下,如果POST请求被浏览器缓存了,那么下单请求就可以不向服务器发请求,而直接返回本地缓存的“下单成功界面”,却又没有真的在服务器下单。那是一件多么滑稽的事情。

因为POST可能有副作用,所以浏览器实现为不能把POST请求保存为书签。想想,如果点一下书签就下一个单,是不是很恐怖?。此外如果尝试重新执行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的编码方式对于文件这种二进制的数据非常低效。

浏览器在POST一个表单时,url上也可以带参数,只要<form action="url">里的url带querystring就行。只不过表单里面的那些用<input>等标签经过用户操作产生的数据都在会在body里

因此一般会泛泛的说“GET请求没有body,只有url,请求数据放在url的querystring中;POST请求的数据在body中“。但这种情况仅限于浏览器发请求的场景。

接口的角度

AJAX或者java中commons-httpclient/okhttp或者是curl,postman之类的工具发出来的GET和POST请求。

此时GET/POST不光能用在前端和后端的交互中,还能用在后端各个子服务的调用中(即当一种RPC协议使用)。这时候就只是单纯用HTTP实现接口发送请求,限制就没有浏览器里那么多了,符合格式就行。
HTTP请求的格式,大概是这样的一个字符串

<METHOD> <URL> HTTP/1.1\r\n
<Header1>: <HeaderValue1>\r\n
<Header2>: <HeaderValue2>\r\n
...
<HeaderN>: <HeaderValueN>\r\n
\r\n
<Body Data....>

其中的<METHOD>可以是GET也可以是POST,或者其他的HTTP Method,如PUT、DELETE、OPTION……。从协议本身看,并没有什么限制说GET一定不能没有body,POST就一定不能把参放到<URL>的querystring上。因此其实可以更加自由的去利用格式。比如Elastic Search的_search api就用了带body的GET;也可以自己开发接口让POST一半的参数放在url的querystring里,另外一半放body里;你甚至还可以让所有的参数都放Header里——可以做各种各样的定制,只要请求的客户端和服务器端能够约定好。

也就是说,这时用POST或者GET没什么区别

番外 REST

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

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

与浏览器的场景类似,REST GET也不应该有副作用,于是可以被反复无脑调用。浏览器(包括浏览器的Ajax请求)对于这种GET也可以实现缓存(如果服务器端提示了明确需要Caching);但是如果用非浏览器,有没有缓存完全看客户端的实现了。

REST 【POST】+ 【资源定位符】则用于“创建一个资源”,比如:

POST http://foo.com/books
{
  "title": "大宽宽的碎碎念",
  "author": "大宽宽",
  ...
}

这里你就能留意到浏览器中用来实现表单提交的POST,和REST里实现创建资源的POST语义上的不同。

顺便讲下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”,通过一组必要的数据创建出完整的资源。至于到底用PUT还是POST创建资源,完全要看是不是提前可以知道资源所有的数据(尤其是id),以及是不是完整替换。比如对于AWS S3这样的对象存储服务,当想上传一个新资源时,其id就是“ObjectName”可以提前知道;同时这个api也总是完整的replace整个资源。这时的api用PUT的语义更合适;而对于那些id是服务器端自动生成的场景,POST更合适一些。

其他细节

安全性

常听到GET不如POST安全,因为POST用body传输数据,而GET用url传输,更加容易看到。但是从攻击的角度,无论是GET还是POST都不够安全,因为HTTP本身是明文协议。每个HTTP请求和返回的每个byte都会在网络上明文传播,不管是url,header还是body。这完全不是一个“是否容易在浏览器地址栏上看到“的问题。为了避免传输中数据被窃取,必须做从客户端到服务器的端端加密。业界的通行做法就是https

编码

常见的说法有,比如GET的参数只能支持ASCII,而POST能支持任意binary,包括中文。但其实从上面可以看到,GET和POST实际上都能用url和body。因此所谓编码确切地说应该是http中url用什么编码,body用什么编码。先说下url。url只能支持ASCII的说法源自于RFC1738。实际上这里规定的仅仅是一个ASCII的子集[a-zA-Z0-9$-_.+!*'(),]。它们是可以“不经编码”在url中使用。比如尽管空格也是ASCII字符,但是不能直接用在url里。那这个“编码”是什么呢?如果有了特殊符号和中文怎么办呢?一种叫做percent encoding的编码方法就是干这个用的,这也就是为啥我们偶尔看到url里有一坨%和16位数字组成的序列。使用Percent Encoding,即使是binary data,也是可以通过编码后放在URL上的。

猜你喜欢

转载自www.cnblogs.com/Nullc/p/12770418.html