ChatGPT解决这个技术问题 Extra ChatGPT

带有请求正文的 HTTP GET

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

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

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

我的问题:

这完全是个好主意吗?

HTTP 客户端会在 GET 请求中使用请求正文时遇到问题吗?

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

优点是允许轻松发送 XML 或 JSON 请求正文,它没有长度限制并且更容易编码(UTF-8)。
如果您追求的是允许请求主体的安全且幂等的方法,您可能需要查看 SEARCH、PROPFIND 和 REPORT。当然,不使用 GET 并拥有请求正文或多或少会破坏缓存。
@fijiaaron:3 年后,从那时起,我获得了编写 Web 服务的丰富经验。这基本上是我过去几年一直在做的事情。我可以肯定地说,将正文添加到 GET 请求中确实是一个非常糟糕的主意。前两个答案就像一块石头。
@Ellesedil:简单地说:使用 GET 优于 POST 的任何优势,都是因为 HTTP 的设计方式而存在的。当您以这种方式违反标准时,这些优势将不复存在。因此,只剩下一个理由使用 GET + 请求正文而不是 POST:美学。不要为了美观而牺牲坚固的设计。
强调 Evert 所说的:“它没有长度限制”。如果您的带有查询参数的 GET 违反了长度限制(2048 年),那么除了将查询字符串信息放入 json 对象(例如,在请求的正文中)之外,还有什么其他选择。

C
Community

Roy Fielding's comment about including a body with a GET request

是的。换句话说,任何 HTTP 请求消息都被允许包含消息体,因此必须考虑到这一点来解析消息。但是,GET 的服务器语义受到限制,因此主体(如果有)对请求没有语义意义。解析的要求与方法语义的要求是分开的。所以,是的,您可以使用 GET 发送正文,但不,这样做从来没有用处。这是 HTTP/1.1 分层设计的一部分,一旦规范被划分(正在进行中),它将再次变得清晰。 ....罗伊

是的,您可以使用 GET 发送请求正文,但它应该没有任何意义。如果您通过在服务器上解析它并根据其内容更改您的响应来赋予它意义,那么您将忽略 the HTTP/1.1 spec, section 4.3 中的此建议:

...如果请求方法不包括为实体主体定义的语义,则在处理请求时应该忽略消息主体。

以及 the HTTP/1.1 spec, section 9.3 中 GET 方法的描述:

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

它指出请求正文不是 GET 请求中资源标识的一部分,只是请求 URI。

更新

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

HTTP 1.1 2014 Spec

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


缓存/代理是您最有可能破坏的两件事,是的。 “语义”只是“制造其他组件的人期望其他组件运行的方式”的另一种说法。如果你违反了语义,你更有可能在人们写的东西希望你尊重这些语义的地方看到东西破裂。
Elasticsearch 是一个相当重要的产品,它在 GET 中使用 HTTP 请求正文。根据他们的手册,HTTP 请求是否应该支持有正文是未定义的。我个人对填充 GET 请求正文感到不舒服,但他们似乎有不同的意见,他们必须知道自己在做什么。 elastic.co/guide/en/elasticsearch/guide/current/…
@iwein 赋予 GET 请求主体含义实际上 违反规范。 HTTP/1.1 指定服务器应该忽略正文,但 RFC 2119 指定允许实施者忽略“应该”子句,如果他们有充分的理由这样做。相反,如果客户端假设更改 GET 主体将不会更改响应,则客户端确实违反了规范。
被称为“HTTP/1.1 规范”的 RFC2616 现已过时。 2014 年,它被 RFC 7230-7237 取代。引用“处理请求时应忽略消息正文”已为 deleted。现在只是“请求消息框架独立于方法语义,即使该方法没有定义消息体的任何用途”第二个引用“GET 方法意味着检索任何信息。 .. 由 Request-URI 标识”为 deleted。所以,我建议编辑答案@Jarl
我知道这是一个旧线程 - 我偶然发现了它。 @Artem Nakonechny 在技术上是正确的,但 new spec“GET 请求消息中的有效负载没有定义的语义;在 GET 请求上发送有效负载正文可能会导致某些现有实现拒绝该请求。” 因此,如果可以避免,这仍然不是一个好主意。
c
caskey

虽然你可以这样做,但只要 HTTP 规范没有明确排除它,我建议避免它,因为人们不希望事情以这种方式工作。 HTTP 请求链中有许多阶段,虽然它们“大部分”符合 HTTP 规范,但您唯一可以放心的是它们的行为将与 Web 浏览器传统使用的一样。 (我正在考虑诸如透明代理、加速器、A/V 工具包等之类的东西。)

这就是 Robustness Principle 背后的精神,大致是“接受的内容要自由,发送的内容要保守”,您不想在没有充分理由的情况下突破规范的界限。

但是,如果您有充分的理由,那就去吧。


稳健性原则是有缺陷的。如果你对你接受的东西很自由,你会得到垃圾,如果你在收养方面取得了任何成功,只是因为你接受了垃圾。这将使您更难改进您的界面。只看HTML。这就是行动中的反抗原则。
我认为协议的采用(和滥用)的成功和广度说明了稳健性原则的价值。
你试过解析真正的 HTML 吗?自己实现它是不可行的,这就是为什么几乎每个人——包括像谷歌(Chrome)和苹果(Safari)这样的真正大玩家,都没有这样做,而是依赖于现有的实现(最终他们都依赖于 KDE 的 KHTML)。这种重用当然很好,但是您是否尝试过在 .net 应用程序中显示 html?这是一场噩梦,因为您要么必须嵌入一个带有问题和崩溃的非托管 IE(或类似)组件,要么使用甚至不允许您选择文本的可用(在 codeplex)托管组件。
HTTP 规范不仅允许带有 GET 请求的正文数据,而且这也是常见的做法:流行的 ElasticSearch 引擎的 _search API 建议使用 JSON 正文中附加的查询的 GET 请求。作为对不完整的 HTTP 客户端实现的让步,它还允许此处的 POST 请求。
@ChristianPietsch,这是今天的普遍做法。四年前不是这样。虽然规范明确允许客户端在请求中选择性地包含(MAY)实体(第 7 节),但 MAY 的含义在 RFC2119 中定义,并且(糟糕的)代理服务器可以在剥离 GET 请求中的实体时符合规范,特别是只要它不崩溃,它就可以通过转发请求标头而不是包含的实体来提供“简化的功能”。同样,在不同协议级别之间进行代理时,有许多关于必须/可能/应该进行哪些版本更改的规则。
N
Naman

如果您尝试利用缓存,您可能会遇到问题。代理不会在 GET 正文中查看参数是否对响应有影响。


使用 ETag/Last-Modified 标头字段以这种方式帮助:当使用“条件 GET”时,代理/缓存可以处理此信息。
@jldupont 缓存使用验证器的存在来了解是否可以重新验证陈旧的响应,但是,它们不用作主缓存键或辅助缓存键的一部分。
您可以通过查询参数中的正文校验和来解决此问题
对于缓存,只需将正文的哈希添加到 url! :)
D
Dave Durbin

restclientREST console 都不支持,但 curl 支持。

HTTP specification 在第 4.3 节中说

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

Section 5.1.1 将我们重定向到第 9.x 节以了解各种方法。它们都没有明确禁止包含消息体。然而...

Section 5.2

Internet 请求所标识的确切资源是通过检查 Request-URI 和 Host 标头字段来确定的。

Section 9.3

GET 方法意味着检索由 Request-URI 标识的任何信息(以实体的形式)。

这共同表明,在处理 GET 请求时,服务器不需要检查 Request-URI 和 Host 标头字段以外的任何内容。

总而言之,HTTP 规范不会阻止您使用 GET 发送消息体,但是如果不是所有服务器都支持它,我不会感到惊讶。


Paw 还可以选择支持带有正文的 GET 请求,但必须在设置中启用它。
“GET 方法意味着检索由 Request-URI 标识的任何信息(以实体的形式)。”那么,拥有一个获取所有实体的 GET 端点在技术上是否非法/错误?例如,GET /contacts/100/addresses 返回具有 id=100 的人的地址集合。
用于测试 REST API 的放心 Java 库不支持带有正文的 GET 请求。 Apache HttpClient 也不支持它。
Django 还支持解析 GET 正文
jmeter 也可以。
R
Rodrirokr

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

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


想知道为什么 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'
复杂查询可能会达到 http 标头最大长度。
它正在阅读将我带到这个问题的 elasticsearch 文档,因为我认为包含正文被认为是不好的做法
它甚至不需要是一个复杂的查询。即使是一个简单的滚动也可以返回一个很长的 scroll_id(在一个有很多分片的集群中),如果在那里添加,这将导致超出最大 url 长度。
Elasticsearch 支持使用 POST 的相同请求。他们只选择在 GET 中允许正文,因为在查询数据时,他们认为 GET 在语义上比 POST 更正确。有趣的是,Elasticsearch 在这个线程中被提及了这么多。我不会使用一个例子(尽管来自一种流行的产品)作为遵循这种做法的理由。
f
fijiaaron

您可以发送带有正文的 GET 或发送 POST 并放弃 RESTish 宗教信仰(这还不错,5 年前只有一个信仰者——他的评论链接在上面)。

两者都不是很好的决定,但发送 GET 正文可能会阻止某些客户端和某些服务器出现问题。

做一个 POST 可能会遇到一些 RESTish 框架的障碍。

Julian Reschke 在上面建议使用像“SEARCH”这样的非标准 HTTP 标头,这可能是一个优雅的解决方案,但它更不可能被支持。

列出可以和不能做上述每一项的客户可能是最有成效的。

无法发送带有正文的 GET 的客户端(据我所知):

XmlHTTPRequest 提琴手

可以发送带有正文的 GET 的客户端:

大多数浏览器

可以从 GET 检索正文的服务器和库:

阿帕奇

PHP

从 GET 中剥离主体的服务器(和代理):

?


当 Content-Length 为 0 或未设置时,Squid 3.1.6 也会剥离 GET 主体,否则即使设置了长度,也会返回 HTTP 411 Length Required
Fiddler 会,但它会警告你。
您是说 SEARCH 方法可能会在此过程中中断吗?如果代理不理解一种方法,他们应该按原样传递它,所以我不太确定你为什么认为它会破坏任何东西......
@fijiaaron 很想看到这个列表更新。我试图在nodeJS中找到一个允许这样做的库,到目前为止还没有。
@tinker 试试 fastify
d
dat

您尝试实现的目标已经用一种更常见的方法完成了很长时间,并且不依赖于使用带有 GET 的有效负载。

您可以简单地构建您的特定搜索媒体类型,或者如果您想要更加 RESTful,请使用 OpenSearch 之类的东西,然后将请求发布到服务器指示的 URI,例如 /search。然后,服务器可以生成搜索结果或构建最终 URI 并使用 303 重定向。

这具有遵循传统 PRG 方法的优点,帮助缓存中介缓存结果等。

也就是说,对于任何非 ASCII 的内容,URI 都会被编码,application/x-www-form-urlencoded 和 multipart/form-data 也是如此。如果您打算支持 ReSTful 场景,我建议您使用它而不是创建另一种自定义 json 格式。


您可以简单地构建您的特定搜索媒体类型您能详细说明一下吗?
我是说您可以创建一个名为 application/vnd.myCompany.search+json 的媒体类型,其中包含您希望客户端发布的搜索模板类型,然后客户端可以将其作为 POST 发送。正如我已经强调的那样,已经有一种媒体类型,它被称为 OpenSearch,当您可以使用现有标准实现您的场景时,应该选择重用现有媒体类型而不是自定义路由。
这很聪明,但过于复杂且效率低下。现在您必须发送一个带有您的搜索条件的 POST,从您的 POST 中获取一个 URI 作为响应,然后将带有搜索条件 URI 的 GET 发送到服务器以获取该条件并将结果发送回给您。 (除了在 URI 中包含 URI 在技术上是不可能的,因为您不能在不超过 255 个字符的内容中发送最多 255 个字符的内容——因此您必须使用部分标识符和您的服务器需要知道如何为您的 POSTed 搜索条件解析 URI。)
A
Adrien

我向 IETF HTTP WG 提出了这个问题。 Roy Fielding(1998 年 http/1.1 文档的作者)的评论是:

“......除了解析和丢弃该主体(如果收到)之外,实现将被破坏以执行任何其他操作”

RFC 7213 (HTTPbis) 指出:

“GET 请求消息中的有效负载没有定义的语义;”

现在似乎很清楚,其意图是禁止 GET 请求正文上的语义含义,这意味着不能使用请求正文来影响结果。

如果您在 GET 中包含一个正文,那么一些代理肯定会以各种方式破坏您的请求。

所以总而言之,不要这样做。


对于记录在案的 REST API,您可能很清楚客户端和服务器之间没有代理,并且发送 JSON 正文可能比在查询字符串中编码相同的细节更清晰的 API。
M
Martin Andersson

GET,有身体!?

规范方面你可以,但是,不明智地这样做不是一个好主意,正如我们将看到的那样。

RFC 7231 §4.3.1 声明主体“没有定义的语义”,但这并不是说它是被禁止的。如果您将正文附加到请求中,那么您的服务器/应用程序从中得到什么取决于您。 RFC 继续声明 GET 可以是“各种数据库记录的编程视图”。显然,这样的视图多次被大量输入参数剪裁,这些输入参数放在request-target的查询组件中并不总是方便甚至安全的。

好处:我喜欢措辞。很明显,一个读取/获取资源而对服务器没有任何可观察到的副作用(该方法是“安全的”),并且无论第一个请求的结果如何,都可以以相同的预期效果重复请求(该方法是“幂等的”)。

坏处: HTTP/1.1 的早期草案禁止 GET 有正文,并且 - 据称 - 直到今天,某些实现甚至会丢弃正文、忽略正文或拒绝消息。例如,一个愚蠢的 HTTP 缓存可能只从请求目标中构造一个缓存键,而忽略了正文的存在或内容。一个甚至更笨的服务器可能是如此无知,以至于它将正文视为一个新请求,这实际上被称为“请求走私”(这是一种“在另一个设备不知道的情况下向一个设备发送请求”的行为 - { 1})。

由于我认为主要是对实现之间的不可操作性的担忧,work in progress 建议将 GET 主体分类为“不应该”,“除非 [请求] 是直接向源服务器发出的之前已经表明,在带内或带外,这样的请求是有目的的,并且会得到充分的支持”(强调我的)。

修复:有一些技巧可以用来解决这种方法的一些问题。例如,body-unware 缓存可以通过将派生自 body 的 hash 附加到查询组件来间接成为 body-aware,或者通过响应服务器的 cache-control: no-cache 标头完全禁用缓存。

唉,当涉及到请求链时,人们通常无法控制甚至不知道所有当前和未来的 HTTP 中介以及它们将如何处理 GET 主体。这就是为什么这种方法通常被认为是不可靠的。

但是POST,不是幂等的!

POST 是另一种选择。 POST 请求通常包含消息正文(仅作记录,正文不是必需的,参见RFC 7230 §3.3.2)。 RFC 7231 (§4.3.3) 中的第一个用例示例是“向数据处理过程提供数据块 [...]”。因此,就像 GET 与 body 一样,后端的 body 会发生什么取决于您。

好处:当一个人希望发送请求正文时,可能是一种更常见的方法,无论出于何种目的,因此可能会从您的团队成员那里产生最少的噪音(有些人可能仍然错误地认为 POST 必须创建一个资源)。

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

坏处:POST请求没有定义为幂等的,导致请求重试犹豫。例如,在页面重新加载时,浏览器不愿意重新提交 HTML 表单,而不用不可读的神秘消息提示用户。

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

QUERY是圣杯

有一个新方法 QUERY 的提议,它确实定义了消息体的语义并且将该方法定义为幂等的。请参阅this

编辑:作为旁注,我在发现一个代码库后偶然发现了这个 StackOverflow 问题,在该代码库中他们仅使用 PUT 请求来实现服务器端搜索功能。这是他们的想法,即包含一个带有参数的主体并且也是幂等的。唉,PUT 的问题在于请求正文具有非常精确的语义。具体来说,PUT“请求创建目标资源的状态或将其替换为[主体中] 的状态”(RFC 7231 §4.3.4)。显然,这排除了 PUT 作为一个可行的选择。


GET 正文将在 HTTP 规范的下一个修订版中升级为“不应该”。没有定义的语义并不意味着“你可以决定语义是什么”,在这种情况下它意味着:“它不应该改变请求的语义”。这不是你的问题,我认为规范中写得不好。目的是主体的存在不应该破坏实现,仅此而已。
请注意,仅针对 GET 提到了“未定义语义”行,而不是像 POST 这样的方法,在这种方法中,广泛接受服务器如何解释正文取决于服务器。不过,关于 POSTQUERY 的一切都是正确的!
我不相信您或我可以对“没有定义的语义”的含义做出任何权威性声明。我们只能从表面上看规范,规范无法给 GET 主体赋予明确的含义,但也足够聪明,不会仅仅因为当时和地点有限的作者集不能就禁止该主体。 t 预期一个用例。我确信我们现在都可以同意这种做法至少有一个用例 - 谢谢 HTTP 家伙!
“数百年前编写的遗留软件可能会丢弃主体或以其他方式忽略它”——AFAIK 这包括所有当前的浏览器。
“你能提供参考吗?” - httpwg.org/http-core/…
C
Community

来自 RFC 2616, section 4.3,“消息正文”:

服务器应该在任何请求上读取并转发消息体;如果请求方法不包括为实体主体定义的语义,则在处理请求时应该忽略消息主体。

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

这与上面菲尔丁的引述一致。

Section 9.3,“GET”,描述了 GET 方法的语义,没有提到请求体。因此,服务器应该忽略它在 GET 请求中收到的任何请求正文。


Section 9.5,“POST”,也没有提到请求体,所以这个逻辑是有缺陷的。
@CarLuva POST 部分说“POST 方法用于请求源服务器接受包含的实体......” entity body 部分说“实体主体是从消息主体中获得的......”因此, POST 部分确实提到了消息体,尽管间接地通过引用 POST 请求的消息体所携带的实体体。
u
user941239

哪个服务器会忽略它? – fijiaaron 2012 年 8 月 30 日 21:27

例如,谷歌做得比忽略它更糟糕,它会认为这是一个错误!

用一个简单的 netcat 自己试试:

$ netcat www.google.com 80
GET / HTTP/1.1
Host: www.google.com
Content-length: 6

1234

(1234内容后面是CR-LF,所以一共是6个字节)

你会得到:

HTTP/1.1 400 Bad Request
Server: GFE/2.0
(....)
Error 400 (Bad Request)
400. That’s an error.
Your client has issued a malformed or illegal request. That’s all we know.

您还会收到来自 AkamaiGhost 服务的 Bing、Apple 等的 400 Bad Request。

所以我不建议将 GET 请求与 body 实体一起使用。


这个例子没有意义,因为通常当人们要向 GET 请求添加正文时,这是因为他们自己的自定义服务器能够处理它。因此,问题是其他“移动部件”(浏览器、缓存等)是否能正常工作。
这是一个错误的请求,因为 GET 在该特定端点上 不期望(或不明智)您的有效负载 - 在一般情况下它与 GET 的使用无关.如果内容不是在特定请求的上下文中有意义的格式,则随机有效负载可以很容易地破坏 POST,并返回相同的 400 Bad Request
不仅在整个端点上,而且在那个特定的 URL 上。
这无关紧要,因为它只是 Google 在该 URL 上的服务器实现。所以这个问题没有意义
对我来说这很有用,因为我试图将 firebase 函数与 get request + body 一起使用,而这个错误可能非常神秘且难以理解。
B
Benjamin W.

根据 XMLHttpRequest,它是无效的。从 standard

4.5.6 send() 方法客户端。 send([body = null]) 发起请求。可选参数提供请求正文。如果请求方法是 GET 或 HEAD,则忽略该参数。如果任一状态未打开或设置了 send() 标志,则引发 InvalidStateError 异常。 send(body) 方法必须运行以下步骤: 如果状态未打开,则抛出 InvalidStateError 异常。如果设置了 send() 标志,则抛出 InvalidStateError 异常。如果请求方法是 GET 或 HEAD,则将 body 设置为 null。如果 body 为空,则进行下一步。

虽然,我认为它不应该,因为 GET 请求可能需要大的正文内容。

所以,如果你依赖浏览器的 XMLHttpRequest,它很可能是行不通的。


由于 XMLHttpRequest 是一个实现这一事实而被否决。它可能无法反映它应该实现的实际规范。
上面的投票是错误的,如果某些实现不支持发送带有 GET 的主体,那么无论规范如何,这可能是不这样做的原因。我实际上在我正在开发的跨平台产品中遇到了这个确切的问题——只有使用 XMLHttpRequest 的平台无法发送 get。
C
Community

如果您真的想将可缓存的 JSON/XML 正文发送到 Web 应用程序,则放置数据的唯一合理位置是使用 RFC4648: Base 64 Encoding with URL and Filename Safe Alphabet 编码的查询字符串。当然,您可以只对 JSON 进行 urlencode 并将其放入 URL 参数的值中,但 Base64 给出的结果较小。请记住,存在 URL 大小限制,请参阅 What is the maximum length of a URL in different browsers?

您可能认为 Base64 的填充 = 字符可能对 URL 的参数值不利,但似乎并非如此 - 请参阅此讨论: http://mail.python.org/pipermail/python-bugs-list/2007-February/037195.html 。但是,您不应该放置没有参数名称的编码数据,因为带有填充的编码字符串将被解释为具有空值的参数键。我会使用 ?_b64=<encodeddata> 之类的东西。


我认为这是一个非常糟糕的主意 :) 但是如果我要做这样的事情,我会改为使用自定义 HTTP 标头(并确保我总是发回 Vary: 在响应中)。
坏与否,但可行:) 对于标题中的数据,数据大小存在类似问题,请参阅 stackoverflow.com/questions/686217/… 。但是,感谢您提到 Vary 标头,我不知道它的真正潜力。
c
cloudhead

我不建议这样做,它违反了标准做法,并且没有提供那么多回报。您希望将正文保留为内容,而不是选项。


X
Xenonite

您有一个选项列表,这些选项比使用带有 GET 的请求正文要好得多。

假设您有每个类别的类别和项目。两者都由 id 标识(在本示例中为“catid”/“itemid”)。您想根据特定“顺序”中的另一个参数“sortby”进行排序。您想为“sortby”和“order”传递参数:

你可以:

使用查询字符串,例如 example.com/category/{catid}/item/{itemid}?sortby=itemname&order=asc 对路径使用 mod_rewrite(或类似):example.com/category/{catid}/item/{itemid} /{sortby}/{order} 使用随请求传递的单个 HTTP 标头 使用不同的方法(例如 POST)来检索资源。

都有其缺点,但比使用 GET 和 body 要好得多。


n
nomail

我很不高兴 REST 作为协议不支持 OOP,Get 方法就是证明。作为一种解决方案,您可以将 DTO 序列化为 JSON,然后创建查询字符串。在服务器端,您可以将查询字符串反序列化为 DTO。

看看:

ServiceStack 中基于消息的设计

使用 WCF 构建基于 RESTful 消息的 Web 服务

基于消息的方法可以帮助您解决 Get 方法限制。您将能够像请求正文一样发送任何 DTO

Nelibur web service framework provides functionality which you can use

var client = new JsonServiceClient(Settings.Default.ServiceAddress);
var request = new GetClientRequest
    {
        Id = new Guid("2217239b0e-b35b-4d32-95c7-5db43e2bd573")
    };
var response = client.Get<GetClientRequest, ClientResponse>(request);

as you can see, the GetClientRequest was encoded to the following query string

http://localhost/clients/GetWithResponse?type=GetClientRequest&data=%7B%22Id%22:%2217239b0e-b35b-4d32-95c7-5db43e2bd573%22%7D

您应该只使用 POST。如果 url 中有方法名称,则违反了基本的 rest 设计。这是 RPC,使用 POST。
我认为这没什么大不了的,我们在使用 RESTful url(即 orders/1)的开发过程中遇到了更多问题。至于我,Get 方法有问题,它与 OOP 不兼容。谁在乎 url 的样子 :) 但是通过基于消息的方法,我们可以创建稳定的远程接口,这非常重要。 PS它不是RPC,它是基于消息的
我认为你错过了 REST 的全部意义。当您说,谁在乎 url 的样子时,REST 很在乎。为什么 REST 会与 OOP 兼容?
我看不出 REST 如何支持或不支持 OOP。
c
chaz

不符合 base64 编码的标头怎么办? “某些应用程序参数:sdfSD45fdg45/aS”

长度限制hm。你不能让你的 POST 处理区分含义吗?如果你想要像排序这样的简单参数,我不明白为什么这会是一个问题。我想你肯定担心。


您可以使用 x- 前缀发送您想要的任何参数,对标头长度的任何限制将完全是服务器的任意限制。
J
Jon49

恕我直言,您可以只在 URL 中发送 JSON 编码(即 encodeURIComponent),这样您就不会违反 HTTP 规范并将您的 JSON 发送到服务器。


是的,但主要问题是长度限制,我们如何处理它?
@Sebas我一直在阅读所有这些答案,希望出现答案......
N
Nick

例如,它适用于 Curl、Apache 和 PHP。

PHP 文件:

<?php
echo $_SERVER['REQUEST_METHOD'] . PHP_EOL;
echo file_get_contents('php://input') . PHP_EOL;

控制台命令:

$ curl -X GET -H "Content-Type: application/json" -d '{"the": "body"}' 'http://localhost/test/get.php'

输出:

GET
{"the": "body"}

有趣的实验!当正文通过 POST 请求和 application/x-www-form-urlencoded 发送时,PHP 只会在 $_POST 中读取。这意味着在 GET 请求中忽略正文。在这种情况下:$_GET$_POST 在这一点上无论如何都非常具有误导性。所以最好使用php://input
F
Frédéric

即使一个流行的工具使用这个,正如这个页面上经常引用的那样,我认为它仍然是一个非常糟糕的主意,它太奇特了,尽管规范没有禁止。

许多中间基础设施可能会拒绝此类请求。

例如,忘记在您的网站前使用一些可用的 CDN,例如 one

如果查看器 GET 请求包含正文,CloudFront 会向查看器返回 HTTP 状态代码 403(禁止访问)。

是的,您的客户端库也可能不支持发出此类请求,如此 comment 中所述。


D
Daniel

关于一个老问题的想法:

在正文上添加内容,在查询字符串上添加正文的哈希,因此缓存不会成为问题,您将能够在需要时发送大量数据:)


并非如此,大多数客户端和服务器中的查询字符串大小都有限制。
B
Bhaskara Arani

创建一个 Requestfactory 类

import java.net.URI;

import javax.annotation.PostConstruct;

import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RequestFactory {
    private RestTemplate restTemplate = new RestTemplate();

    @PostConstruct
    public void init() {
        this.restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestWithBodyFactory());
    }

    private static final class HttpComponentsClientHttpRequestWithBodyFactory extends HttpComponentsClientHttpRequestFactory {
        @Override
        protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
            if (httpMethod == HttpMethod.GET) {
                return new HttpGetRequestWithEntity(uri);
            }
            return super.createHttpUriRequest(httpMethod, uri);
        }
    }

    private static final class HttpGetRequestWithEntity extends HttpEntityEnclosingRequestBase {
        public HttpGetRequestWithEntity(final URI uri) {
            super.setURI(uri);
        }

        @Override
        public String getMethod() {
            return HttpMethod.GET.name();
        }
    }

    public RestTemplate getRestTemplate() {
        return restTemplate;
    }
}

和@Autowired 在您需要和使用的任何地方,这是一个带有 RequestBody 的示例代码 GET 请求

 @RestController
 @RequestMapping("/v1/API")
public class APIServiceController {
    
    @Autowired
    private RequestFactory requestFactory;
    

    @RequestMapping(method = RequestMethod.GET, path = "/getData")
    public ResponseEntity<APIResponse> getLicenses(@RequestBody APIRequest2 APIRequest){
        APIResponse response = new APIResponse();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Gson gson = new Gson();
        try {
            StringBuilder createPartUrl = new StringBuilder(PART_URL).append(PART_URL2);
            
            HttpEntity<String> entity = new HttpEntity<String>(gson.toJson(APIRequest),headers);
            ResponseEntity<APIResponse> storeViewResponse = requestFactory.getRestTemplate().exchange(createPartUrl.toString(), HttpMethod.GET, entity, APIResponse.class); //.getForObject(createLicenseUrl.toString(), APIResponse.class, entity);
    
            if(storeViewResponse.hasBody()) {
                response = storeViewResponse.getBody();
            }
            return new ResponseEntity<APIResponse>(response, HttpStatus.OK);
        }catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<APIResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        
    }
}

好吧,那是一些代码……但问题不是要代码。它询问这是否是一个好主意(否)以及客户是否会遇到问题(是)。
对我有用!