HTTP GET带有请求体

3066

我正在为我们的应用程序开发一个新的RESTful webservice。

当客户端对某些实体进行GET操作时,客户端可以请求实体的内容。如果他们想添加一些参数(例如对列表进行排序),他们可以将这些参数添加到查询字符串中。

或者,我希望人们能够在请求正文中指定这些参数。HTTP/1.1似乎并没有明确禁止这样做。这将使他们能够指定更多信息,可能会使指定复杂的XML请求变得更容易。

我的问题:

  • 总体而言,这是个好主意吗?
  • 使用请求正文在GET请求中会导致HTTP客户端出现问题吗?

https://www.rfc-editor.org/rfc/rfc2616


612
优点在于可轻松发送XML或JSON请求体,没有长度限制,更易编码(UTF-8)。 - Evert
38
如果您需要一个安全且幂等的方法,并允许请求体,则可以考虑使用SEARCH、PROPFIND和REPORT。当然,不使用GET并具有请求体会或多或少地破坏缓存。 - Julian Reschke
304
三年过去了,这段时间我积累了大量编写 Web 服务的经验。实际上,在过去的几年里,这几乎是我的全部工作内容。我可以肯定地说,将请求体添加到 GET 请求中确实是个非常糟糕的想法。前两个答案就像一块巨石般稳固。 - Evert
38
简单来说,使用GET相较于POST的优点是由HTTP设计决定的。但是,一旦你违反了这个标准,这些优点就不存在了。因此,使用GET和请求正文代替POST只有一个原因:美观。不要为了美观而牺牲稳健的设计。 - Evert
21
强调Evert所说的:“它没有长度限制”。如果您使用查询参数的GET请求超出了长度限制(2048),那么除了将查询字符串信息放入JSON对象(例如请求正文)中,还有什么选择。 - Kieran Ryan
显示剩余34条评论
24个回答

2452

Roy Fielding关于在GET请求中包含主体的评论

是的。换句话说,任何HTTP请求消息都允许包含消息正文,并且因此必须考虑解析带有该正文的消息。然而,对于GET的服务器语义是受限制的,因此如果有正文,则对请求没有语义意义。解析要求与方法语义的要求是分开的。

因此,是的,您可以使用GET发送正文,但是不,这永远没有用。

这是HTTP / 1.1分层设计的一部分,一旦规范被分区(正在进行中),它将再次变得清晰。

....罗伊

是的,你可以使用GET发送请求正文,但它不应该有任何含义。如果您通过在服务器上解析它并根据其内容更改响应来赋予它含义,则忽略了HTTP / 1.1规范第4.3节中的此建议:

......如果请求方法不包括实体正文的定义语义,则在处理请求时应忽略消息正文SHOULD

以下是HTTP/1.1规范第9.3节中GET方法的描述:

GET方法意味着检索由请求URI标识的任何信息([...])。

这表明,在GET请求中,请求正文不是资源标识的一部分,只有请求URI。

更新

被称为“HTTP/1.1规范”的RFC2616现已过时。在2014年,它被RFC7230-7237取代。引用“处理请求时应忽略消息正文”已被删除。现在只是“请求消息框架与方法语义无关,即使方法没有定义消息正文的使用”。第二个引用“GET方法意味着检索由请求URI标识的任何信息…”已被删除。-来自评论

HTTP 1.1 2014规范中:

GET请求消息中的有效载荷没有定义的语义;在GET请求上发送有效载荷正文可能会导致某些现有实现拒绝请求。


108
缓存/代理是你最可能破坏的两个东西,确实如此。 "语义"只是另一种说法,意思是“制造其他组件的人期望其他组件运行的方式”。如果你违反了语义,你更有可能在那些写代码时期望你遵守那些语义的地方看到错误发生。 - Stuart P. Bentley
184
Elasticsearch是一个相当重要的产品,它在GET请求体中使用HTTP请求。根据它们的手册,HTTP请求是否支持具有请求体是未定义的。个人而言,我不太喜欢填充GET请求的请求体,但是他们似乎有不同的看法,并且他们必须知道自己在做什么。详见https://www.elastic.co/guide/en/elasticsearch/guide/current/_empty_search.html。 - GordonM
37
@iwein 给 GET 请求添加请求正文并不违反规范。[HTTP/1.1] (https://tools.ietf.org/html/rfc2616#section-4.3) 规定服务器应该忽略请求正文,但是RFC 2119 规定,如果实现者有充分的理由这样做,允许他们忽略“应该”条款。换句话说,如果客户端假设更改 GET 请求正文不会更改响应,则确实违反了规范。 - Emil Lundberg
144
引用“HTTP/1.1规范”的RFC2616现已过时。在2014年,它被RFC7230-7237所取代。在RFC7230的第3.3节中,引用“_在处理请求时,应忽略消息体_”已经被删除。现在只是说“即使方法未定义任何消息体的使用,请求消息的格式化也与方法语义无关。”在RFC7231的第4.3.1节中,第二个引用“GET方法表示检索由请求URI标识的任何信息”,也被删除了。因此,我建议修改@Jarl的答案。 - Artem Nakonechny
46
我知道这是一个旧的线程 - 我偶然发现的。 @Artem Nakonechny在技术上是正确的,但是新规范指出:“GET请求消息中的负载没有定义的语义; 在GET请求中发送有效载荷可能会导致某些现有的实现拒绝该请求。”因此,如果可以避免,仍然不是一个真正好的主意。 - fastcatch
显示剩余29条评论

376

虽然在HTTP规范中没有明确禁止这样做,你是可以这么做的,但我建议避免这样做,因为人们不希望以这种方式操作。HTTP请求链有许多阶段,尽管它们“大多数”符合HTTP规范,但你所能保证的只有它们会按照Web浏览器的传统用法行事。(我考虑的是像透明代理、加速器、A /V工具包等东西。)

这是稳健性原则的精神大致上是“在接受时要宽松,在发送时要谨慎”,你不希望无故扩展规范的边界。

然而,如果你有充分的理由,请去做。


278
强健原则存在缺陷。如果您在接受内容时过于宽容,您将得到垃圾内容,并且如果您在采纳方面获得任何成功,那只是因为您接受了垃圾内容。这将使界面的演进更加困难。只需看看HTML就可以看出这种强健原则的影响。 - Evgeniy Berezovsky
37
我认为这些协议的成功和广泛应用(以及滥用)证明了鲁棒性原则的价值。 - caskey
51
你尝试过解析真实的HTML吗?自己实现不可行,这就是为什么几乎所有人 - 包括像Google(Chrome)和苹果(Safari)这样的大公司,都没有这样做,而是依赖于现有的实现(最终它们都依赖于KDE的KHTML)。这种重用当然很好,但你尝试在.NET应用程序中显示HTML吗?这是一场噩梦,因为你要么必须嵌入一个未管理的IE(或类似)组件,带有其问题和崩溃,要么使用可用的(在CodePlex上)托管组件,甚至无法允许你选择文本。 - Evgeniy Berezovsky
13
HTTP规范不仅允许在GET请求中包含正文数据,而且这也是常见的做法:流行的ElasticSearch引擎的_search API建议在JSON正文中附加查询的GET请求。为了迁就不完整的HTTP客户端实现,该引擎还允许在此处使用POST请求。 - Christian Pietsch
8
今天这是常规做法,但在四年前不是。虽然规范明确允许客户端在请求中包含(MAY)一个实体(第7节),但MAY的含义在RFC2119中有定义,而且一个(糟糕的)代理服务器可以符合规范,在GET请求中剥离实体,只要它不崩溃,就可以通过转发请求标头而不是包括实体来提供“减少功能”。同样,在代理不同协议级别时,必须/可以/应该进行哪些版本更改也有一系列规则。 - caskey
显示剩余5条评论

249

如果您尝试利用缓存,可能会遇到问题。代理服务器不会查看GET请求体以查看参数是否影响响应。


15
使用ETag/Last-Modified头字段可以帮助实现条件GET。当使用条件GET时,代理服务器/缓存可以利用这些信息进行操作。 - jldupont
5
缓存使用验证器来判断过期的响应是否能够重新验证,但是验证器并不作为主要或次要缓存键的一部分。 - Darrel Miller
16
你可以通过在查询参数中添加正文的校验和来修复这个问题。 - Adrian May
2
对于缓存,只需将Body的哈希值添加到URL中即可! :) - hex

96

Elasticsearch接受带有请求正文的GET请求。甚至似乎这是首选方式:Elasticsearch指南

一些客户端库(如Ruby驱动程序)可以在开发模式下将哭泣命令记录到标准输出,并广泛使用该语法。


6
想知道为什么Elasticsearch允许这样做。这意味着用于计算所有具有有效负载的文档的查询,可以作为GET请求的一部分:curl -XGET 'http://localhost:9200/_count?pretty' -d ' { "query": { "match_all": {} } }'等同于将有效负载包含在“source”参数中:curl -XGET 'http://localhost:9200/_count?pretty&source=%7B%22query%22%3A%7B%22match_all%22%3A%7B%7D%7D%7D' - arun
63
复杂查询可能会触发HTTP标头的最大长度限制。 - s.Daniel
13
阅读Elasticsearch文档后,我认为将请求体包含在内被认为是不良实践,因此才产生了这个问题。 - PatrickWalker
1
甚至不需要复杂的查询。即使是简单的滚动操作也可能返回一个非常长的scroll_id(在具有许多分片的集群中),如果将其添加到URL中,就会超出最大URL长度。 - Brent Hronik
31
Elasticsearch支持使用POST进行相同的请求。他们之所以选择在GET中允许主体是因为他们认为在查询数据方面,GET比POST更符合语义学。有趣的是,在这个话题中经常提到Elasticsearch。我不会仅仅因为一个例子(尽管来自流行产品)而将其作为遵循该实践的理由。 - dso
@DSO 我觉得他们的论点是正确的,这是一个搜索和GET请求,它恰好有很多参数无法适应URL。在我看来,缓存实现也应该考虑到正文。我一直觉得在GET请求中将参数编码在URL中很麻烦,只需传递正文会更好。但是问题在于您不能使用浏览器在URL栏中修改此类请求。 - rubyprince

92

无论是restclient还是REST控制台都不支持此功能,但curl可以。

HTTP规范在第4.3节中指出:

如果请求方法的规范(第5.1.1节)不允许在请求中发送实体主体,则不得在请求中包含消息主体。

第5.1.1节将我们重定向到第9.x节以获取各种方法。它们中没有一个明确禁止包含消息主体。然而...

第5.2节说:

“通过检查请求URI和主机头字段来确定Internet请求所识别的确切资源。”第9.3节说:“GET方法意味着检索由请求URI标识的任何信息(以实体形式)。”这表明,在处理GET请求时,服务器不需要检查除请求URI和主机头字段之外的任何内容。总之,HTTP规范不会阻止您使用GET发送消息正文,但存在足够的歧义,使我不会惊讶如果并非所有服务器都支持它。”

2
Paw还可以支持带有请求体的GET请求,但必须在设置中启用。 - s.Daniel
"GET方法意味着检索由请求URI标识的任何信息(以实体形式)。那么,是否在GET端点中获取所有实体在技术上是非法/错误的?例如:GET /contacts/100/addresses 返回具有id = 100的人的地址集合。" - Josh M.
1
rest-assured Java库用于测试REST API,不支持带有请求体的GET请求。Apache HttpClient也不支持。 - Paulo Merson
Django还支持解析GET请求体。 - Bruno Finger
jmeter 也可以。 - Mahesh C. Regmi
@JoshM。我认为在HTTP规范语言中,地址是一个实体,地址列表也是一个实体。 - Eneko

85

GET带有请求体?

从规范上来看,你是可以的,但是不明智地这样做是不好的,正如我们所看到的。

RFC 7231 §4.3.1指出请求体“没有定义的语义”,但这并不意味着禁止这样做。如果你在请求中附加一个请求体,你的服务器/应用程序会对其进行解释。RFC进一步指出,GET可以是“对各种数据库记录的编程视图”。显然,这种视图很多时候需要大量的输入参数,而这些参数并不总是方便或安全地放在请求目标的查询组件中。

好处:我喜欢这种措辞。很清楚,使用此方法读取/获取资源不会对服务器产生任何可观测的副作用(该方法是“安全的”),而且无论第一次请求的结果如何,都可以重复请求以达到相同的预期效果(该方法是“幂等的”)。

缺点: HTTP/1.1 的早期草稿禁止 GET 请求具有正文,据称一些实现甚至直到今天都会删除正文、忽略正文或拒绝消息。例如,一个愚蠢的 HTTP 缓存可能仅从请求目标构造缓存键,对正文的存在或内容毫不知情。更加愚蠢的服务器可能如此无知,以至于将正文视为新请求,这有效地被称为“请求走私”(即“向一个设备发送请求,而另一个设备不知道”的行为-来源)。

由于我认为主要是因为实现方面的问题,正在进行的工作建议将 GET 正文归类为“SHOULD NOT”,除非[该请求]直接发给已经明确指出过这样的请求具有目的并将得到充分支持的原始服务器(强调是我的)。

解决方法:对于此方法存在的问题,可以采用一些小技巧来解决。例如,通过在查询组件中附加从主体派生的哈希值,无法识别主体的缓存将间接地变得可识别主体,或通过从服务器响应 cache-control: no-cache 标头来完全禁用缓存。

然而,当涉及到请求链时,人们通常不能控制或甚至意识到所有现有和未来的 HTTP 中介者以及它们如何处理 GET 请求主体。因此,这种方法必须被视为不可靠的。

但是,POST 不具有幂等性!

POST 是一个可替代的方法。POST 请求通常包括一个消息主体(只是为了记录,主体并非必需,参见 RFC 7230 §3.3.2)。RFC 7231 的第一个用例示例 (§4.3.3) 就是“向数据处理过程提供数据块”。因此,就像带有主体的 GET 请求一样,后端如何处理主体取决于您。

优点:可能是应用请求主体时更常用的方法,无论出于什么目的,所以,很可能会产生最少的团队成员噪音(有些人仍然错误地认为 POST 必须创建资源)。

此外,我们常常将参数传递给一个操作不断演变的数据的搜索函数,只有在响应中提供了明确的新鲜度信息时,POST响应才是可缓存的。

问题所在:POST请求没有被定义为幂等的,这导致请求重试存在犹豫。例如,在页面重新加载时,浏览器不愿意在未提示用户的情况下重新提交HTML表单。

解决方式:即使POST没有被定义为幂等,也并不意味着它不能是幂等的。事实上,RFC 7230 §6.3.1 写道:“知道(通过设计或配置)向给定资源发出POST请求是安全的用户代理可以自动重复该请求”。因此,除非您的客户端是HTML表单,否则这可能不是真正的问题。

QUERY是至高无上的

有一个新方法的提案,叫做QUERY,它定义了消息体的语义,并将该方法定义为幂等。请参见此文档

编辑:顺便说一下,我在发现一个代码库只使用PUT请求进行服务器端搜索功能后,偶然遇到了这个StackOverflow问题。这是他们想要通过将参数作为请求体来实现幂等性的想法。然而,PUT请求的问题在于请求体具有非常精确的语义。具体来说,PUT“请求使用请求体中的状态创建或替换目标资源的状态”(RFC 7231 §4.3.4)。显然,这排除了PUT作为可行选项。


1
在HTTP规范的下一个版本中,GET主体将升级为“不应该”。没有定义的语义并不意味着“你可以决定语义是什么”,它只意味着在这种情况下:“它不应该改变请求的语义”。这很混乱,并不是因为你,我认为规范写得很差。意图是主体的存在不应该破坏实现,没有更多的要求。 - Evert
1
我不相信你或我能够对“未定义的语义”做出任何权威性的声明。我们只能按照规范的字面意思去理解,规范可能无法给GET请求体一个明确定义的含义,但也足够聪明,不会禁止请求体,因为当时和那个地方的这个有限的作者群体无法预见使用情况。正如我们现在都可以同意的一样,实践确实至少有一个用例 - 感谢HTTP的开发者们! - Martin Andersson
2
据我所知,这包括每个当前的浏览器:数百年前编写的遗留软件可能会丢弃或忽略它。 - Quentin
1
请问您能提供一个参考文献吗?- https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.9.3.1.p.6 - Quentin
1
谢谢Evert!我读了这篇文章,写得非常好,而且幽默!我认为很多人不会反对你关于为什么不应该包含GET body的观点。但我仍然不能同意像“body显然是不允许的”这样的说法。因为它显然是允许的哈哈。你自己使用的引用非常准确:“除非直接向先前已经在或离线指示此类请求具有目的并将得到充分支持的源服务器发出请求,否则客户端不应在GET请求中生成内容”。 - Martin Andersson
显示剩余8条评论

42

你可以使用带有主体的GET或发送POST并放弃RESTish信仰(这并不是很糟糕,5年前只有一个该信仰的成员——他的评论链接在上面)。

两种方法都不是很好,但发送带有主体的GET可能可以防止一些客户端和服务器的问题。

执行POST可能会在一些RESTish框架中遇到障碍。

Julian Reschke建议使用非标准HTTP头,例如“SEARCH”,这可能是一种优雅的解决方案,但更不可能被支持。

最具生产力的做法可能是列出每个方法可行和不可行的客户端。

无法发送带有主体的GET请求的客户端(我所知道的):

  • XmlHTTPRequest
  • Fiddler

可以发送带有主体的GET请求的客户端:

  • 大多数浏览器

可以从GET请求中检索主体的服务器和库:

  • Apache
  • PHP

可能会从GET请求中删除主体的服务器(和代理):


4
Squid 3.1.6在Content-Length为0或未设置时也会剥离GET请求体,并且如果长度已设置,则发送HTTP 411 Length Required状态码,即使长度已设置。 - rkok
4
Fiddler会这样做,但它会向你发出警告。 - toddmo
你是在说SEARCH方法可能会在中途出错吗?如果代理不理解一个方法,它们应该按原样通过,所以我不太确定你为什么认为它会导致任何错误... - Alexis Wilke
@fijiaaron很希望能看到这个列表被更新。我正在尝试在nodeJS中找到一个允许这样做的库,但目前没有找到。 - tinker
@tinker 试试Fastify。 - SILENT
显示剩余2条评论

41
我向IETF HTTP WG提出了这个问题。Roy Fielding(1998年http/1.1文档的作者)的评论是:
“……如果收到GET请求的主体,除了解析和丢弃该主体之外,实现将会出错。” RFC 7231(HTTPbis)指出:
“GET请求消息中的有效载荷没有定义的语义;”
现在看来很明确,意图是禁止在GET请求主体上使用语义含义,这意味着请求主体不能用于影响结果。
如果在GET请求中包含主体,那么存在一些代理服务器会以各种方式破坏您的请求。
所以总结起来,不要这样做。

对于一个有文档的 REST API,你很可能知道客户端和服务器之间没有代理,并且发送 JSON 数据体可能比在查询字符串中编码相同的细节更清晰。 - Mark Stosberg

41
你想要达到的目标早已经被一个更为常见的方法实现了,而且不需要使用GET负载。你可以简单的构建特定的搜索Mediatype,或者如果你想更符合RESTful风格,可以使用类似OpenSearch的东西,并将请求POST到服务器指定的URI,比如/search。服务器可以生成搜索结果或构建最终URI并使用303重定向。这样做有利于遵循传统的PRG方法,帮助缓存中介缓存结果等等。话虽如此,除了ASCII之外,任何东西都会被编码,application/x-www-form-urlencoded和multipart/form-data也是如此。如果你的意图是支持RESTful场景,我建议你使用它们,而不是创建另一种自定义的JSON格式。

5
您可以简单地构建特定的搜索媒体类型。 您能详细说明一下吗? - Piotr Dobrogost
3
我所说的是,你可以创建一种名为“application/vnd.myCompany.search+json”的媒体类型,其中包含你想让客户端发出的搜索模板。客户端可以将其作为POST发送。正如我所强调的那样,已经有一个名为OpenSearch的媒体类型可用于此目的,如果可以使用现有标准来实现你的方案,应该选择重复使用现有媒体类型而不是自定义路线。 - SerialSeb
18
聪明,但过于复杂且低效。现在你需要发送一个带有搜索条件的POST请求,并从POST的响应中获取URI,在将搜索条件URI发送给服务器以获取结果之前,还需要发送一个GET请求。 (除非在URI中包含一个URI在技术上是不可能的,因为你不能在最多255个字符的内容中发送可长达255个字符的内容——因此,你必须使用部分标识符,然后你的服务器需要知道如何解析POST的搜索条件URI。) - fijiaaron

25

根据RFC 2616第4.3节, “消息正文”:

服务器应在任何请求上读取和转发消息正文;如果请求方法未包含实体主体的已定义语义,则处理请求时应忽略消息正文。

也就是说,服务器应始终从网络中读取任何提供的请求正文(检查Content-Length或读取分块正文等)。此外,代理应转发它们收到的任何此类请求正文。然后,如果RFC对于给定方法的正文定义了语义,服务器可以实际使用请求正文生成响应。但是,如果RFC 定义正文的语义,则服务器应将其忽略。

这与上面来自Fielding的引用一致。

第9.3节,“GET”描述了GET方法的语义,并且没有提到请求正文。因此,服务器应忽略在GET请求上接收到的任何请求正文。


“POST”(第9.5节)也没有提到请求正文,因此这种逻辑是错误的。 - CarLuva
10
@CarLuva 的POST部分说:“POST方法用于请求源服务器接受封装的实体...”而实体主体 部分说:“实体主体是从消息主体获得的…”因此,POST部分虽然通过引用实体主体间接提到了消息主体,但仍然提到了消息主体。 - frederickf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接