1. 概述

本文将探讨为什么HTTP GET请求不应该包含请求体。首先,我们分析这种请求的常见使用场景,接着说明其潜在问题,最后提供几种替代方案。

2. 为什么有些GET请求会带有请求体?

在开发REST API时,我们可能会遇到需要在GET请求中附加数据的情况。这通常发生在以下场景:

  • 执行复杂搜索(如Elasticsearch的搜索API)
  • URI过长导致服务器无法处理

这种做法源于对早期HTTP/1.1规范的解读,后来因便利性而流行起来。

2.1. HTTP/1.1规范不明确

RFC 7231规范对GET请求体的描述存在歧义:

GET请求消息中的载荷没有明确定义的语义。

这句话引发了多种解读:

  • ✅ 语义由各实现者自行决定
  • ❌ 这种请求根本不存在语义

结果导致:

  • 部分HTTP服务器允许带请求体的GET
  • 浏览器、负载均衡器、缓存代理等中间件则不支持

2.2. 便利性驱动的选择

开发者选择带请求体的GET主要出于三个原因:

  1. 编码便利性
    JSON比复杂查询字符串更结构化,且所有编程语言都支持。Elasticsearch的搜索API就采用了这种设计:

    相比晦涩的查询字符串,请求体搜索允许我们使用查询领域特定语言(query DSL)编写查询。

  2. 可控环境
    当客户端和服务器都在自己掌控中时,直接在请求体传递数据更可靠。

  3. 语义直观性
    用带请求体的GET获取数据比用POST更符合直觉。毕竟POST通常用于创建资源。Elasticsearch作者就明确表示:

    我们更倾向于用GET进行搜索,因为它比POST更能准确描述“获取信息”这个动作。但由于带请求体的GET未被广泛支持,我们的搜索API也接受POST请求。

3. 为什么在GET请求中使用请求体是个坏主意?

虽然带请求体的GET在可读性和开发者体验(DX)上更优,但实际开发中应极力避免这种做法。

3.1. RFC 9110规范合规性

HTTP规范演进明确反对这种做法:

  • RFC 7231(2014年)警告:

    GET请求中的载荷没有明确定义语义;发送载荷可能导致某些现有实现拒绝请求。

  • RFC 9110(2019年)进一步收紧:

    客户端不应在GET请求中生成内容,除非直接向源服务器发送且该服务器已明确表示支持此类请求。源服务器不应依赖私有协议接收内容,因为HTTP通信参与者通常不了解请求链中的中间件。

⚠️ 这彻底解决了HTTP/1.1的歧义问题。为保障跨系统兼容性,避免在GET中使用请求体是明智之选。

3.2. 请求体被忽略

HTTP/1.1最初建议忽略GET请求体:

如果请求方法未为实体主体定义语义,则处理请求时应忽略消息主体。

部分旧服务器会直接丢弃请求体,导致功能异常。

3.3. 请求被拒绝

许多HTTP实现会直接拒绝带请求体的GET请求:

  • 浏览器的fetch API会抛出错误
  • Spring框架会抛出HttpMessageNotReadableException

3.4. 请求无法被缓存

RFC 9110规定GET请求应可缓存,但带请求体的GET会破坏这一特性:

  • 代理服务器不会读取请求体
  • 导致缓存失效
  • 最终因多次服务器调用引发性能问题

4. 带请求体的GET请求有哪些替代方案?

4.1. HTTP POST

最简单的替代方案是使用POST:

  • POST是限制最少的HTTP方法
  • ✅ 总是安全的选择
  • 当语义符合时,应优先使用更具体的方法

当你试图给GET添加请求体时,改用POST总是没错的。—— Roy Fielding(HTTP规范作者之一)

4.2. HTTP QUERY

QUERY方法是一个新兴提案,专门用于:

  • 安全、可缓存、幂等的带内容请求
  • 接受包含查询操作的载荷

示例请求:

QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: example/query
Accept: text/csv

select surname, givenname, email limit 10

示例响应:

HTTP/1.1 200 OK
Content-Type: text/csv

surname, givenname, email
Smith, John, john.smith@example.com
Jones, Sally, sally.jones@example.com
Dubois, Camille, camille.dubois@example.com

⚠️ 目前仍是草案阶段,尚未广泛实现。

非标准的SEARCH方法,支持JSON/XML请求体:

SEARCH /search HTTP/1.1
Host: www.example.com
Content-Type: application/json

{
  "query": "baeldung",
  "sort": "date",
  "filters": {
    "type": "article",
    "category": "tech"
  }
}

特点:

  • 服务器可解析请求体中的所有字段
  • 目前仅用于WebDAV规范
  • 未被广泛采用

5. 结论

本文系统分析了带请求体的GET请求的来龙去脉:

  1. 起源于HTTP/1.1规范的模糊表述
  2. 因便利性而被部分项目采用
  3. 但会引发兼容性、缓存、中间件支持等问题
  4. 当前最佳实践是使用POST替代
  5. 未来可能有QUERY等标准化方案

始终遵循最新的HTTP规范是保障系统互操作性的关键。当遇到复杂查询需求时,简单粗暴地改用POST才是明智之选。


原始标题:Why an HTTP Get Request Shouldn’t Have a Body