关于休息:HTTP GET请求正文

HTTP GET with request body

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

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

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

我的问题:

  • 这是个好主意吗?
  • HTTP客户端在使用GET请求中的请求主体时会有问题吗?

http://tools.ietf.org/html/rfc2616


罗伊·菲尔丁(RoyFielding)关于将尸体与GET请求一起包含在内的评论。

Yes. In other words, any HTTP request message is allowed to contain
a message body, and thus must parse messages with that in mind.
Server semantics for GET, however, are restricted such that a body,
if any, has no semantic meaning to the request. The requirements
on parsing are separate from the requirements on method semantics.

So, yes, you can send a body with GET, and no, it is never useful
to do so.

This is part of the layered design of HTTP/1.1 that will become
clear again once the spec is partitioned (work in progress).

....Roy

是的,您可以使用get发送请求主体,但它不应该有任何意义。如果您通过在服务器上分析它并根据其内容更改响应来赋予它意义,那么您将忽略HTTP/1.1规范第4.3节中的建议:

[...] if the request method
does not include defined semantics for an entity-body, then the
message-body SHOULD be ignored when handling the request.

以及http/1.1规范第9.3节中get方法的说明:

The GET method means retrieve whatever information ([...]) is identified by the Request-URI.

声明请求主体不是GET请求中资源标识的一部分,而是请求URI。

更新引用为"http/1.1 spec"的rfc2616现在已过时。2014年被RFC7230-7237替代。引号"处理请求时应忽略消息正文"已被删除。它现在只是"请求消息框架独立于方法语义,即使方法没有为消息体定义任何用途","第二个引号"get方法意味着检索任何信息…由请求URI标识"已删除。-从评论中


虽然您可以这样做,但在HTTP规范没有明确排除的范围内,我建议您避免这样做,因为人们不希望事情以这种方式工作。HTTP请求链中有许多阶段,虽然它们"大部分"都符合HTTP规范,但唯一能保证的是,它们的行为将与Web浏览器传统使用的行为相同。(我在考虑透明代理、加速器、A/V工具包等。)

这就是稳健性原则背后的精神,大致上是"接受的内容要自由,发送的内容要保守",您不想在没有充分理由的情况下推进规范的边界。

但是,如果你有一个很好的理由,那就去做吧。


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


restclient和rest控制台都不支持这一点,但curl支持。

HTTP规范如第4.3节所述

A message-body MUST NOT be included in a request if the specification of the request method (section 5.1.1) does not allow sending an entity-body in requests.

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

第5.2节说明

The exact resource identified by an Internet request is determined by examining both the Request-URI and the Host header field.

第9.3节说

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

这一起表明,在处理GET请求时,服务器不需要检查请求URI和主机头字段之外的任何内容。

总之,HTTP规范并没有阻止您使用GET发送消息体,但是有足够的模糊性,如果不是所有服务器都支持它,我不会感到惊讶。


ElasticSearch接受带有主体的GET请求。甚至似乎这是首选的方式:http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/common options.html _request_body_in_query_string

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


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

您可以简单地构建特定的搜索中介类型,或者,如果您想更安静,可以使用OpenSearch之类的工具,并将请求发布到指定服务器的URI,比如/search。然后,服务器可以生成搜索结果,或者构建最终的URI并使用303重定向。

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

也就是说,uri是为任何非ASCII的内容编码的,application/x-www-form-urlencoded和multipart/form数据也是如此。如果您的目的是支持RESTful场景,那么我建议您使用它,而不是创建另一个自定义JSON格式。


Which server will ignore it? – fijiaaron Aug 30 '12 at 21:27

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

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

1
2
3
4
5
6
$ netcat www.google.com 80
GET / HTTP/1.1
Host: www.google.com
Content-length: 6

1234

(1234内容后跟CR-LF,总共6个字节)

你会得到:

1
2
3
4
5
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.

你也会收到400个来自必应、苹果等的错误请求…由Akamaighost提供服务。

所以我不建议在实体实体中使用GET请求。


你可以发送一个关于身体的GET,或者发送一个帖子,然后放弃不稳定的宗教信仰(这并不坏,5年前只有一个信仰的成员——他的评论链接在上面)。

这两者都不是很好的决定,但是发送一个get主体可能会防止一些客户机和一些服务器出现问题。

在一些RESTish框架中,发布可能会遇到障碍。

JulianReschke在上面建议使用一个非标准的HTTP头,比如"search",这可能是一个很好的解决方案,只是它不太可能被支持。

列出能够并且不能完成上述每一项的客户机可能是最有效的。

无法通过正文发送GET的客户端(我知道的):

  • XMLHttpRequest小提琴手

可以通过正文发送GET的客户端:

  • 大多数浏览器

可以从get中检索主体的服务器和库:

  • 阿帕奇
  • PHP

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


来自RFC2616,第4.3节,"消息正文":

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.

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

这与上面fielding的引用一致。

第9.3节"get"描述了get方法的语义,没有提到请求体。因此,服务器应该忽略它在GET请求上接收到的任何请求主体。


我把这个问题提交给IETF HTTP工作组。罗伊·菲尔丁(1998年HTTP/1.1文件的作者)的评论是

"... an implementation would be broken to do anything other than to parse and discard that body if received"

RFC 7213(httpbis)声明:

"A payload within a GET request message has no defined semantics;"

现在看来,意图是禁止对GET请求主体的语义含义,这意味着请求主体不能用来影响结果。

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

总之,不要这样做。


如果您真的想将cachable-json/xml-body发送到Web应用程序,唯一合理的放置数据的地方是使用rfc4648:base 64编码的查询字符串,使用URL和文件名安全字母表进行编码。当然,您可以只对json进行urlencode,并将其放入url参数的值中,但base64给出的结果要小一些。请记住,存在URL大小限制,请参见不同浏览器中URL的最大长度是多少?.

您可能认为base64的padding =字符可能不利于url的param值,但它似乎不适合-请参阅以下讨论:http://mail.python.org/pipermail/python-bugs-list/2007-february/037195.html。但是,不应该将没有参数名称的编码数据放入,因为带填充的编码字符串将被解释为具有空值的参数键。我会用像?_b64=这样的东西。


根据xmlhttpRequest,它是无效的。根据标准:

4.5.6 The send() method

1
client . send([body = null])

Initiates the request. The optional argument provides the request
body. The argument is ignored if request method is GET or HEAD.

Throws an InvalidStateError exception if either state is not
opened or the send() flag is set.

send(body)方法必须运行以下步骤:

  • 如果状态未打开,则引发InvalidStateError异常。
  • 如果设置了send()标志,则抛出InvalidStateError异常。
  • 如果请求方法是GetHEAD,则将body设置为空。
  • 如果body为空,则转到下一步。
  • < /块引用>

    不过,我不认为应该这样做,因为GET请求可能需要大量的内容。

    因此,如果您依赖于浏览器的xmlhttpRequest,它很可能不会工作。


    不一致的base64编码头怎么样?"一些thingpp-params:sdfsd45fdg45/as"

    长度限制嗯。你不能让你的后处理区分意思吗?如果你想要简单的参数,比如排序,我不明白为什么会有问题。我想你肯定很担心。


    例如,它与curl、apache和php一起工作。

    PHP文件:

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

    控制台命令:

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

    输出:

    1
    2
    GET
    {"the":"body"}


    imho您只需发送URL中的JSON编码(即encodeURIComponent编码),这样您就不会违反HTTP规范,并将JSON发送到服务器。


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


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

    看一看:

    • ServiceStack中基于消息的设计
    • 使用WCF构建基于消息的RESTful Web服务

    基于消息的方法可以帮助您解决get方法的限制。您可以通过请求正文将任何DTO发送给

    Nelibur Web Service Framework提供您可以使用的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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


    您有一个选项列表,比使用带有get的请求主体要好得多。

    假设每个类别都有类别和项目。两者都由一个ID标识("catid"/"itemid"用于本例)。您希望根据另一个参数"sortby"按特定的"order"进行排序。要传递"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要好得多。