HTTP GET 请求可以有 body 吗?

您所在的位置:网站首页 get请求的参数在哪 HTTP GET 请求可以有 body 吗?

HTTP GET 请求可以有 body 吗?

2023-03-23 09:25| 来源: 网络整理| 查看: 265

本文有点啰嗦,没耐心的直接拉到末尾看结论。

在网上可以经常看到关于 HTTP GET 请求能不能带 body 的讨论。有的人认为 GET 请求可以带 body,有的认为 GET 请求不能带 body,还有些人认为可以带但最好不带。大家各执己见,谁都没有 100% 说服谁。

我个人在工作中从来没有见哪个同事用 HTTP GET 请求的 body 携带数据。写了两年爬虫,经常抓包分析目标网站的请求数据,也没见到哪个网站的 GET 请求携带 body。看 HTTP 协议的时候也没特别注意关于这方面的规定。所以我一直默认为不会有人把数据放在 HTTP GET 请求的 body 里,但不知道 GET 携带 body 是否符合规范。今天专门梳理一下。

HTTP 协议对 GET 请求 body 的规定RFC 1945

RFC 1945 发布于 1996 年,描述了 HTTP/1.0 。

其中和 body 有关的第 7 节提到了一下内容:

Full-Request and Full-Response messages may transfer an entity within some requests and responses. An entity consists of Entity-Header fields and (usually) an Entity-Body. 译:Full-Request 和 Full-Response 消息可以在某些请求和响应中传输实体。实体包括实体首部字段,并且通常包括一个实体 body。

说明 HTTP/1.0 会通过请求或响应的 body 传输实体,并且没有限定哪些请求方法不能传输实体。也就是说 GET 也可以有 body。

另外其 8.1 节关于 GET 和 POST 方法有如下描述:

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. 译:GET 方法表示查询由 Request-URI 标识的任何信息(形式为实体)。 The POST method is used to request that the destination server accept the entity enclosed in the request ... 译:POST 方法用于请求目标服务器接受包含在请求中的实体 ...

说明 GET 方法的语义是请求实体,POST 方法的语义是提交实体,两者有明确的分工。

RFC 1945 这两处的内容可以提炼出两条信息:

请求或响应需要传输实体时才会有 body。GET 请求用于请求实体,而不是传输实体。

根据这两条信息可以推出,GET 请求没有传输实体的语义,自然也不需要 body。但 RFC 1945 也没有明确规定 GET 请求不能传输实体、不能有 body。所以按 HTML 1.0 规范,GET 请求是可以有 body 的,只不过没有为其定义语义。

RFC 2068

RFC 2068 发布于 1997 年,描述了 HTTP/1.1。

RFC 2068 是对 RFC 1945 的更新,在 4.3 节有以下描述:

A message-body MAY be included in a request only when the request method allows an entity-body. 译:只有当请求方法允许使用实体 body 时,请求中才可以包含消息 body。

第 9 节是关于各个 HTTP 请求方法的描述,但只有第 9.8 节提到:

A TRACE request MUST NOT include an entity. 译:TRACE 请求必须不能包含实体。

另外,对 PUT、POST 的描述都默认有实体。但是对 GET 描述并没有提到请求中是否能包含实体 body。

也就是说 TRACE 不允许包含 body,PUT、POST 请求包含 body,但是 GET 没有明确说明,这种没说明的情况到底是允许还是不允许呢?

RFC 2616

RFC 2616 发布于 1999 年,是对 RFC 2068 的更新,还是描述的 HTTP/1.1。

其 4.3 节增加了如下描述:

A message-body MUST NOT be included in a request if the specification of the request method 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. 译:如果本规范规定了某个请求方法不允许发送实体,则绝不能在请求中包含消息 body。服务器应该读取和转发任何请求的消息体(body);如果某个请求方法没有定义实体语义,那么在处理请求时应该忽略消息体(body)。

但第 9 节对于各个方法的描述中还是没有说 GET 请求是否能有 body。

RFC 7231

2004 年发布的 RFC 7230~7235 是对 RFC 2626 的修订。其中 RFC 7231 是 HTTP 的“核心”语义规范,终于在 4.3.1 节明确提到了 GET 请求的 body:

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)没有定义的语义;在 GET 请求上发送有效负载主体可能会导致某些现有实现拒绝该请求。

说明修订规范的人也知道有的 HTTP server 实现会拒绝带有 body 的 HTTP GET 请求。但是为什么规范迭代了这么多次都不规定每个请求方法是否能包含 body 呢?留下这么大的争议空间也是醉了。

现有实现对 GET 请求 body 的支持浏览器中的 GET 请求XMLHttpRequest

在 XMLHttpRequest 规范 中有如下描述:

If this’s request method is GET or HEAD, then set body to null.

实际在 chrome 浏览器中测试时,用 XMLHttpRequest 发送 GET 请求并带 body,body 参数会被忽略。

说明从规范到实际,XMLHttpRequest 都不支持 GET 请求 body。

Fetch

在 chrome 浏览器中测试时,Fetch 发送带 body 的 GET 请求会报错:

Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.HTTP 请求库

测试用常用的 HTTP 请求库发出带 body 的请求,并抓包验证。

Requests

Requests 是 Python 最流行的 HTTP 请求库。

import requests requests.get( url="http://127.0.0.1:8080", proxies={"http": "http://127.0.0.1:8888", "https": "https://127.0.0.1:8888"}, data={"name": "x"}, )

抓包的结果如下:

GET http://127.0.0.1:8080/ HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: python-requests/2.27.1 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Length: 6 Content-Type: application/x-www-form-urlencoded name=x

说明 Requests 支持 GET 请求 body。

HTTPX

HTTPX 是 Python 正在崛起的 HTTP 请求库。

HTTPX 的便捷方法 httpx.get 并没有提供填充 body 的参数,只能用高级方法 httpx.request。如下:

import httpx rep = httpx.request( method="GET", url="http://127.0.0.1:8080", json={"name": "x"}, proxies={"http://": "http://127.0.0.1:8888", "https://": "https://127.0.0.1:8888"}, )

通过 fiddler 抓包到的请求数据如下:

GET http://127.0.0.1:8080/ HTTP/1.1 Host: 127.0.0.1:8080 Accept: */* Accept-Encoding: gzip, deflate Connection: keep-alive User-Agent: python-httpx/0.21.3 Content-Length: 13 Content-Type: application/json {"name": "x"}

说明 HTTPX 支持 GET 请求 body。

OkHttp

todo...

axios

axios 是一个基于 Promise 的 HTTP Client 库,可用于浏览器和 Node.js。也是非常流行,在 JavaScript 技术栈中的地位类似 Requests 在 Python 世界的地位。

在 Node.js 中用 axios 发出一个带 body 的 GET 请求:

import axios from "axios"; await axios.get("https:/baidu.com", { data: { name: "x" }, proxy: { host: "127.0.0.1", port: 8888 }, });

抓包发出的请求:

GET https://null/baidu.com HTTP/1.1 Accept: application/json, text/plain, */* Content-Type: application/json User-Agent: axios/0.24.0 Content-Length: 12 host: null Connection: close {"name":"x"}

说明 Node.js 中的 axios 支持 GET 请求 body。

superagent

一款小型渐进式客户端 HTTP 请求库,支持许多高级 HTTP 客户端功能。

import superagent from "superagent"; import superagentProxy from "superagent-proxy"; superagentProxy(superagent); superagent .get("http://127.0.0.1") .proxy("http://127.0.0.1:8888") .send({ name: "x" }) .end();

抓包结果:

GET http://127.0.0.1/ HTTP/1.1 Host: 127.0.0.1 Accept-Encoding: gzip, deflate Content-Type: application/json Content-Length: 12 Connection: close {"name":"x"}node-fetch

node-fetch 是 Node.js 的一个 HTTP 请求库,和浏览器的 window.fetch 保持一致。尝试用 node-fetch 3.1.0 发送带 body 的 GET 请求:

import fetch from "node-fetch"; import { env } from "process"; env["http_proxy"] = "http://127.0.0.1:8888"; env["https_proxy"] = "https://127.0.0.1:8888"; const response = await fetch("http://127.0.0.1:8080", { method: "GET", body: JSON.stringify({ name: "x" }), }); await response.json();

结果报错如下:

TypeError: Request with GET/HEAD method cannot have body

看来 node-fetch 明确不支持 GET 请求 body,和浏览器的 window.fetch 保持一致。

node.js 内置模块 httpimport http from "http"; const req = http.request({ method: "GET", path: "https://baidu.com", host: "127.0.0.1", port: 8888, }); let data = JSON.stringify({ name: "x" }); req.setHeader("Content-Length", data.length); req.end(data);

抓包结果:

GET https://baidu.com HTTP/1.1 Host: baidu.com Content-Length: 12 Connection: close {"name":"x"}

说明 node 的内置 http 模块也是支持发送 GET 请求 body 的。

常用 HTTP 工具curl

curl 可能是最常用的 http 命令行工具了。

$ export http_proxy=http://127.0.0.1:8888 $ export https_proxy=http://127.0.0.1:8888 $ curl -X GET -d "name=x" http://baidu.com

抓包结果:

GET http://baidu.com/ HTTP/1.1 Host: baidu.com User-Agent: curl/7.79.1 Accept: */* Connection: Keep-Alive Content-Length: 6 Content-Type: application/x-www-form-urlencoded name=x

说明 curl 也是支持 GET 请求 body 的。

Postman

经过测试,可以。

Apifox

经过测试,可以。

Fiddler

Fiddler 是常用的 HTTP 抓包工具,也可以用来发送 HTTP 请求。发个带 body 的 GET 请求试试。

Fiddler 发送带 Body 的 GET 请求

可以看出 Fiddler 是可以发出带 body 的 GET 请求的,但是会用红色表示警告。

nginx

nginx 非常强度,通常用作 HTTP server 或反向代理 server。

经过测试 nginx 也支持带 body 的 GET 请求。

Web Server 框架Flask

Flask 是 Python 社区最流行的 Web Server 框架。用 Flask 写一个 HTTP Api,看看能不能处理 GET 请求的 body。

代码如下:

from flask import Flask, request app = Flask(__name__) @app.get("/") def get(): print(request.form["name"]) return f"you name is {request.form['name']}" app.run(host="127.0.0.1", port=8080)

该应用接收一个 GET 请求并解析 body 中的表单参数。

启动后,另起一个终端,用 curl 发出带 body 的 GET 请求:

$ curl -X GET -d "name=x" http://127.0.0.1:8080/ you name is x

可以看出 Flask 确实正确处理了本次 GET 请求的 body。

FastAPI

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架。已经遮住了 Flask 的光芒。

用 FastAPI 写一个 HTTP Api,看看能不能处理 GET 请求的 body。代码如下:

// main.py from fastapi import Body, FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str @app.get("/") async def update_item(item: Item): results = {"name": item.name} return results

执行以下命令将其启动:

$ uvicorn flask-test:app INFO: Started server process [8532] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

然后用 curl 发送一个带 body 的 GET 请求到应用:

$ curl -X GET -H "Content-Type: application/json" -d "{\"name\":\"x\"}" http://127.0.0.1:8000/ {"name":"x"}

可以看出 FastAPI 也正确处理了本次 GET 请求的 body。

其他 Web Server 框架

本来打算继续测试一批 Node.js 的 web server 框架、一批 Java 的主流 web server 框架,但精力有限。而且前面测试的库和工具大多都支持 GET 请求带 body,再测试下去结论也不会变,所以就不再测试了。以后有时间再说吧。

结论

HTTP 协议没有为 GET 请求的 body 赋予语义,也就是即不要求也不禁止 GET 请求带 body。

大多数 HTTP 实现从技术上都支持 HTTP GET 请求带 body,少数实现会禁止(google-chrome 浏览器、node-fetch),少数实现会不建议(Fiddler)。

个人建议

本人没啥影响力,给不了最佳实践,仅代表一家之言,如果你不同的见解,欢迎讨论。

软件工程中有一条原则:不要依赖未定义的行为。HTTP 协议未定义 GET 请求的 body 语义,如果想用 GET 请求发送 body,得先为其定义语义,并确保上下游都能很好的支持。作为服务接口的提供方,不应该假设所有的调用方都能发出 GET 请求 body;作为调用方,不应该假设服务方能完美解析 GET 请求 body,但如果服务方提供了支持 GET 请求 body 的接口,可以放心使用,不用纠结。

软件工程中还有另一条原则,不记得原文了,翻译成中国的老话就是:严于律己,宽已待人。我们在写库、写框架、写工具时应该支持 GET 请求带 body;在封装接口时,尽量不要强制调用方用 GET body 提交数据,除非遇到用 GET body 才符合逻辑的特殊情况;在使用别人提供的库、框架、工具,或者调用协作方提供的接口时不应该强求对方支持 GET 请求 body。

参考

RFC 1945

RFC 2068

RFC 2616

RFC 7231

rest - HTTP GET with request body - Stack Overflow

Elasticsearch: The Definitive Guide | A GET Request with a Body?



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3