谁说 HTTP GET 就不能通过 Body 来发送数据呢?

当我们被问及 HTTP 的 GET 与 POST 两种请求方式的区别的时候,很多答案是说 GET 的数据须通过 URL 以 Query Parameter 来传送,而 POST 可以通过请求体来发送数据,所以因 URL 的受限,往往 GET 无法发送太多的字符。这个回答好比在启用了 HTTPS 时,GET 请求 URL 中的参数仍然是明文传输的一样。

GET 果真不能通过 Request Body 来传送数据吗?非也。如此想法多半是因循着网页中 form 的 method 属性只有 get 与 post 两种而来。因为把 form 的 method 设置为 post, 表单数据会放在 body 中,而 method 为 get(默认值) 时, 提交时浏览器会把表单中的字符拼接到 action 的 URL 后作为 query parameter 传送。于是乎就有了这么一种假像:HTTP GET 必须通过 URL 的查询参数来发送数据。

其实 HTTP 规范并未规定说 GET 就不能发送 body 数据,在 RFC GET 中只是说

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

只是说 GET 意味着通过 URI 来识别资源。

我也是本着传统上对 GET 与 POST 区别的误解很多年,今天突然意识到 GET 应该可以使用 body, 况且 HTTP 本身是一个纯文本的协议。没有测试就没有 100% 的发言权,所以做了如下的测试

在一个 Spring Boot Web 项目中创建的 GET 请求 API

上而创建的 GET 请求,URL 是 /?id=something, 然后希望通过 request body 来获得请求数据

再来一个测试用例,给 GET 请求发送 body 数据

上面的单元测试顺利通过,说明对于 GET 请求我们同样可以使用 Request Body 来发送数据,而且 Spring 的测试框架也支持 GET 发送 body 数据。

再作一个验证,curl 命令, 需要用 -X 指定为 GET 请求,否则 curl 在使用 -d 发送 body 数据时自动切换为 POST 请求

通过  curl -v 可以看到详细的请求响应数据,两个请求的 Content-Length 都是 8, 即 "Get Body" 的长度,它们确实是在 Request Body 中,服务端接送 GET 来的 body 数据也没有半点问题。

下面是通过 Wireshark 捕获到的数据包的样子

如果说通过 Spring 的测试用例以及 curl 命令还有所疑问的话,看上面那张图片就分明的告诉我们是在使用 GET 发送 body 数据的。

但确实有些工具或类库不让我们发送 GET 请求时设置 Body, 例如著名的 Postman, 在选择 GET 时 Body 标签是灰色不可用的

而且从目前最新的 Apache Http Client 4.5 组件,它的 HttpGet 也不支持设置 Request Body, 因为 HttpGet 没有像 HttpPost 那样的 setEntity(entity) 方法。

另一个 OkHttpClient 库也不支持 GET 发送 Request Body, 当执行下面的代码时

直接告诉我

java.lang.IllegalArgumentException: method GET must not have a request body

最后再试一个 AsyncHttpClient 库

输出 "100: Get Body", 证明 AsyncHttpClient 是可以 GET 时发送 Body 数据的。

小结一下:

Apache Http Client 和  OkHttpClient 都不支持  GET 请求发送 Body 数据,而 AsyncHttpClient 是可以的。

那么回过头来想想为什么 HTTP 并未规定不可以 GET 中发送 Body 内容,但却不少知名的工具不能用 GET 发送 Body 数据,所以大致的讲我们仍然不推荐使用 GET 携带 Body 内容,还有可能某些应用服务器也会忽略掉 GET 的 Body 数据(???, 猜的)。我想更主要是 GET 被设计来用 URI 来识别资源,如果让它的请求体中携带数据,那么通常的缓存服务便失效了,URI 不能作为缓存的 Key。

但另一方面,如果仅仅是为了读取资源,而需要使用 Body 发送一大批数据时,改用 POST 请求却与 RESTFul 的 POST 语义不相符。这时候或许可以 GET + BODY, 但是不能对该请求以 URI 作为 Key 进行缓存了。

链接:1. Testing the Web Layer


[注: 2018-08-06] rfc2616 已过期,新的规范要看 rfc7230-7235

在 rfc7231 中对 GET 的说明明确了

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.

本文链接 https://yanbin.blog/why-http-get-cannot-sent-data-with-reuqest-body/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

22 Comments
Inline Feedbacks
View all comments
trackback

[…] 谁说 HTTP GET 就不能通过 Body 来发送数据呢? | 隔叶黄莺 Yanbin Blog – 软件… […]

trackback

[…] 谁说 HTTP GET 就不能通过 Body 来发送数据呢? […]

xiaohei*
xiaohei*
4 years ago

通过 Dsl.asyncHttpClient()的方式你,怎样实现发送https的请求?希望能帮助解答一下

xws
xws
5 years ago

关于get请求body传参,并不推荐通过body传参,可以参考stackoverflow关于该问题的回答
https://stackoverflow.com/questions/978061/http-get-with-request-body

Yanbin
5 years ago
Reply to  xws

是的,实现上并不保证能传输,接收 get body 数据

xws
xws
5 years ago
Reply to  Yanbin

我在CSDN上看到你关于http get body传参的帖子,跳转到这里。把这个问题在在CSDN上的说明下不推荐get body方式传参,以免误导新人.

CXM
CXM
5 years ago

他只是幫你把body串在網址列後面了

laixintao
6 years ago

很多人讨论GET和POST的时候很容易就从“协议”讨论到“实现”上去了。 协议里说的是,GET是从服务器取回数据,POST是发送数据,HTTP请求有header,有body。但是实现怎么样,协议就不管了。本文章的HTTPClient,curl,postman,浏览器,这些都是实现。

所以我觉得讨论GET和POST区别的前提是,弄明白协议和实现的区别。比如别人可以说对于Chrome,get的区别是不能带body,这就没问题了。

LXL
LXL
5 years ago
Reply to  laixintao

然而协议里确实说了哪些方法带body是没有意义并可能会产生问题,所以你硬给get加一个body也是属于不遵循规范的,尽管可能成功但是后果自担。所以说get不能带body是正确的说法

Yanbin
5 years ago
Reply to  LXL

A message-body MUST NOT be included in
a request if the specification of the request method (section 5.1.1)
does not allow sending an entity-body in requests. A server SHOULD
read and forward a message-body on any request; if the request method
does not include defined semantics for an entity-body, then the
message-body SHOULD be ignored when handling the request.

这是 RFC 中 message-body 中的描述,但是没有说 GET 不能携带 body,看到 HEAD 和 OPTIONS 忽略 body

LXL
LXL
5 years ago
Reply to  Yanbin

你看的协议RFC2616已经过时了,请看最新的7230-7237

Yanbin
5 years ago
Reply to  LXL

本文并没有建议去违反语义在 GET 请求中传递 body。由于 HTTP/1.1 是基于文本的协议,所以头后空一行后的数据都是 body,所以协议本身未作限制,但是有一个语义上的建议--不应在 GET 请求中放 body 这里有个比较好的回答 https://stackoverflow.com/questions/978061/http-get-with-request-body?answertab=active#tab-top Yes. In other words, any HTTP request message is allowed to contain a message body, and thus must parse messages with that in mind. Server semantics for GET, however, are restricted such that a body, if any, has no semantic meaning to the request. The requirements on parsing are separate from the requirements on method semantics. So, yes, you can send a body with GET, and no, it is never useful to do so. This is part of the layered design of HTTP/1.1 that will become clear again… Read more »

LXL
LXL
5 years ago
Reply to  Yanbin

RFC 7231中这才是最新的说法: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. 因而按照规范,GET等是不可以加body的,否则出问题了不要怪规范没有提醒你

蓝多qwe
蓝多qwe
6 years ago

楼主,在浏览器里如何让get请求携带body呢?

蓝多qwe
蓝多qwe
6 years ago
Reply to  Yanbin

对啊,我发现XHR内部就把body给移除了。