curl GET 请求发 JSON 服务器收不到

FreeGuideOnline 最新 2026-07-04

为什么使用 curl 发送 GET 请求并携带 JSON 数据时,服务器经常收不到?

很多开发者在调用 REST API 时,会尝试用类似下面的命令,在 GET 请求中发送 JSON 数据:

curl -X GET http://example.com/api -H "Content-Type: application/json" -d '{"key":"value"}'

结果却是:服务器日志显示请求体为空,或者直接忽略了你的 JSON 数据。这篇教程会从根本上解释这一现象的原因,并给出正确、可用的替代方案。


1. 现象复现:一个“经典”的错误范例

首先,我们模拟一个最简单的服务端。这里用 Python 的 Flask 框架快速搭建一个 API,它会将接收到的请求方法和请求体打印出来:

服务端代码(server.py

from flask import Flask, request

app = Flask(__name__)

@app.route('/api', methods=['GET', 'POST'])
def api():
    print(f"Method: {request.method}")
    print(f"Body: {request.get_data(as_text=True)}")
    return {"received_body": request.get_data(as_text=True)}

if __name__ == '__main__':
    app.run(debug=True, port=5000)

启动服务后,运行以下 curl 命令:

curl -X GET http://localhost:5000/api \
     -H "Content-Type: application/json" \
     -d '{"name":"Alice","age":30}'

你会发现服务端打印的输出是:

Method: GET
Body: 

Body 为空! 即便你在 curl 中指定了 -d 参数,服务器也收不到任何请求体数据。


2. 根本原因:HTTP 规范对 GET 请求体的约定

出现上述现象的核心原因,并非 curl 没有发送数据,而是 HTTP 协议规范以及大量中间件、框架、库对 GET 请求体的处理方式所导致的

2.1 HTTP 规范并未禁止 GET 带请求体

从 RFC 7231(HTTP/1.1 语义与内容)来看,规范本身并没有明确禁止 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.

(GET 请求中的载荷没有定义语义;在 GET 请求上发送请求体可能会导致某些现有实现拒绝该请求。)

关键点在于 “no defined semantics”(没有定义语义)。这意味着,即使你通过原始 TCP 连接成功将数据放在 GET 请求的 body 中,服务器端的框架或中间件也完全可以忽略它,因为它们不认为 GET 请求的 body 有任何业务含义。

2.2 现实世界中的“忽略”行为

在实际开发中,以下三层都会导致 GET 请求体被丢弃:

  • Web 服务器 / 反向代理:例如 Nginx、Apache,默认配置下可能会丢弃 GET 请求的 body,或者在转发到后端应用时将其剥离。
  • 应用框架:大多数 Web 框架(Express.js、Flask、Django、Spring MVC 等)在解析 GET 请求时,根本不会去读取请求体。因为按照 REST 语义,GET 是安全且幂等的,参数应当通过 URI 的查询字符串传递。
  • 网络中间件:某些 CDN、防火墙或负载均衡器也会忽略或拒绝携带 body 的 GET 请求。

这就解释了为什么 curl -X GET ... -d '...' 从客户端看似乎发出去了,但服务器端代码却拿到一个空的 body。


3. curl 本身的行为:它确实会发送,但……

如果你在本地抓包(例如使用 Wireshark)或者使用 curl 的 verbose 模式,会发现 curl 实际上是可以将 -d 参数的内容作为请求体发送出去的,即使是 GET 方法。例如:

curl -v -X GET http://localhost:5000/api -d "test"

在 verbose 输出中,你可以看到类似下面的行:

> GET /api HTTP/1.1
> Host: localhost:5000
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
test

所以问题不在 curl,而在于接收方。 服务器端框架在读到 HTTP 方法为 GET 后,直接跳过了 body 的解析,因为它认为 GET 请求不应该有 body。


4. 正确的替代方案

既然不能指望服务器从 GET 请求体中读取 JSON,我们该如何传递给服务端结构化数据呢?有几种符合标准且可靠的方式。

4.1 使用 POST 请求(最推荐)

如果你的操作涉及发送数据(尤其是 JSON),最符合 REST 规范的做法是使用 POST(或 PUT、PATCH)。

curl -X POST http://localhost:5000/api \
     -H "Content-Type: application/json" \
     -d '{"name":"Alice","age":30}'

此时服务端可以正确读取请求体,并且这也是所有框架、中间件都完全支持的方式。如果后端 API 只允许 GET,你可以考虑推动 API 设计变更,因为“携带 JSON 的 GET”本身就违背了 REST 的设计原则。

4.2 将 JSON 数据放入查询字符串(Query String)

如果必须使用 GET,且需要传递少量参数,可以把 JSON 字符串作为查询参数的值进行传递。

方案 A:直接传递编码后的 JSON

curl -G http://localhost:5000/api \
     --data-urlencode 'json={"name":"Alice","age":30}'

-G 会让 curl-d 的数据作为查询参数附加到 URL 后面,并自动进行 URL 编码。服务端收到后,从查询参数 json 中取出字符串再反序列化即可。

方案 B:将 JSON 对象的字段拆分为多个查询参数

对于简单结构,直接拆分成多个参数更清晰:

curl "http://localhost:5000/api?name=Alice&age=30"

4.3 使用 curl--data-urlencode 配合 GET(不推荐,仅作了解)

curl 允许你使用 --data-urlencode 发送 URL 编码的数据,并强制使用 GET:

curl -G http://localhost:5000/api \
     --data-urlencode 'name=Alice' \
     --data-urlencode 'age=30'

这本质也是将数据放进查询字符串,而不是请求体。

4.4 极特殊场景:在受控环境中使用带 body 的 GET

如果你同时控制客户端、服务端以及中间的所有代理,并且确实需要利用 GET 请求体传递数据(例如某些内部工具间的调用),你可以:

  • 使用 curl 发送(curl 本身支持)
  • 在自定义的 TCP 服务或者底层 HTTP 库中直接读取 body

但请注意,这种做法极易在后续架构演进(加入网关、负载均衡、日志系统等)时引发问题,非常不推荐。


5. 常见疑问与误区

5.1 为什么 curl 不加 -X GET 时,-d 会自动变成 POST?

curl 的默认行为是:当你使用 -d(或 --data)时,如果未通过 -X 显式指定方法,curl 会自动将请求方法设为 POST。这符合大多数用户的使用预期。而一旦你用 -X GET 强制覆盖,curl 就会按照你的指令发送 GET 请求,但仍然带上 body —— 从而掉进本文描述的陷阱。

5.2 Elasticsearch 的搜索 API 不是用 GET 带 body 吗?

确实,Elasticsearch 的许多搜索端点支持使用 GET 并携带 JSON 查询体。但这属于一种“事实标准”的特例,并且其官方客户端库通常都进行了特殊处理。大多数通用 Web 框架并不会自动支持这种用法。除非你明确知道你的服务端框架和部署链路都支持,否则不要依赖该行为。


6. 总结

  • 现象curl -X GET 搭配 -d 发送 JSON,服务器收不到请求体。
  • 原因:HTTP 规范未定义 GET 请求体的语义,绝大多数 Web 框架、中间件会直接丢弃 GET 请求的 body。
  • 根本原则:REST 风格中,GET 请求应该使用 URI 查询参数传递信息;需要发送 JSON 到服务器时,应优先使用 POST(或 PUT、PATCH)。
  • 推荐做法
    • 改用 POST 请求传递 JSON。
    • 如果必须用 GET,将 JSON 编码后放入查询字符串(使用 --data-urlencode-G)。
  • 强烈避免:在生产环境中依赖 GET 请求体传递数据。

理解了这些底层原理,你就能避免这个常见的“坑”,写出更健壮、更符合标准的 API 调用代码。