我正在为我们的应用程序开发新的RESTful Web服务。
在某些实体上执行GET时,客户端可以请求实体的内容。如果他们想添加一些参数(例如,对列表进行排序),则可以在查询字符串中添加这些参数。
另外,我希望人们能够在请求正文中指定这些参数。 HTTP / 1.1似乎没有明确禁止这样做。这将使他们能够指定更多信息,可能使指定复杂的XML请求更加容易。
我的问题:
- 这是个好主意吗?
- HTTP客户端在GET请求中使用请求主体时会遇到问题吗?
我正在为我们的应用程序开发新的RESTful Web服务。
在某些实体上执行GET时,客户端可以请求实体的内容。如果他们想添加一些参数(例如,对列表进行排序),则可以在查询字符串中添加这些参数。
另外,我希望人们能够在请求正文中指定这些参数。 HTTP / 1.1似乎没有明确禁止这样做。这将使他们能够指定更多信息,可能使指定复杂的XML请求更加容易。
我的问题:
Answers:
罗伊·菲尔丁(Roy Fielding)的评论,其中包括一个带有GET请求的物体。
是。换句话说,任何HTTP请求消息都允许包含消息正文,因此必须在解析消息时牢记这一点。但是,GET的服务器语义受到限制,以使主体(如果有的话)对请求没有语义含义。解析要求与方法语义要求分开。
因此,是的,您可以使用GET发送正文,否,这样做永远没有用。
这是HTTP / 1.1分层设计的一部分,一旦对规范进行了分区(工作正在进行中),它将再次变得清晰。
....罗伊
是的,您可以使用GET发送请求正文,但它没有任何意义。如果您通过在服务器上对其进行解析并根据其内容更改响应来赋予它含义,那么您将忽略HTTP / 1.1规范第4.3节中的建议:
[...]如果请求方法不包括用于一个实体主体定义的语义,则该消息体应该被处理请求时忽略。
以及HTTP / 1.1规范第9.3节中 GET方法的描述:
GET方法意味着检索Request-URI标识的任何信息([...])。
声明请求主体不是GET请求中资源标识的一部分,仅是请求URI。
更新 现在称为“ HTTP / 1.1规范”的RFC2616已过时。在2014年,它被RFC 7230-7237取代。引用“处理请求时应忽略消息正文”已被删除。现在只是“请求消息框架独立于方法语义,即使该方法未定义消息主体的任何用法”,第二个引号“ GET方法意味着检索任何由请求URI标识的信息……”已被删除。-来自评论
尽管可以做到这一点,但在HTTP规范未明确禁止的范围内,我建议避免这样做仅是因为人们不希望事情以这种方式起作用。HTTP请求链中有很多阶段,尽管它们“大部分”都符合HTTP规范,但您唯一可以确定的是它们的行为将与Web浏览器的传统用法相同。(我正在考虑透明代理,加速器,A / V工具包等)
这是“ 健壮性原则 ” 背后的精神,即“在接受的内容上是自由的,在发送的内容上是保守的”,您不想在没有充分理由的情况下突破规范的界限。
但是,如果您有充分的理由,那就去吧。
如果您尝试利用缓存,则可能会遇到问题。代理将不会在GET主体中查看参数是否对响应有影响。
restclient和REST控制台都不支持此功能,但是curl支持。
的HTTP规范说,在第4.3节
如果请求方法的规范(第5.1.1节)不允许在请求中发送实体,则消息体不得包含在请求中。
5.1.1节将各种方法重定向到9.x节。它们都没有明确禁止包含消息正文。然而...
5.2节说
通过检查Request-URI和Host标头字段来确定Internet请求标识的确切资源。
和第9.3节说:
GET方法意味着检索由Request-URI标识的任何信息(以实体形式)。
两者共同表明,在处理GET请求时,不需要服务器检查Request-URI和Host标头字段以外的任何内容。
总而言之,HTTP规范并没有阻止您使用GET发送消息正文,但是有足够的歧义,如果不是所有服务器都支持的话,它也不会令我感到惊讶。
GET /contacts/100/addresses
返回具有的人的地址集合id=100
。
Elasticsearch接受带有主体的GET请求。甚至似乎这是首选方式:Elasticsearch指南
一些客户端库(例如Ruby驱动程序)可以在开发模式下将cry命令记录到stdout,并且它正在广泛使用此语法。
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'
您试图实现的目标已经用一种更为通用的方法完成了很长时间,并且该方法不依赖于在GET中使用有效负载。
您可以简单地构建您特定的搜索媒体类型,或者如果您想要更RESTful,请使用诸如OpenSearch之类的东西,然后将请求发布到服务器指示的URI,例如/ search。然后,服务器可以生成搜索结果或构建最终URI,并使用303进行重定向。
这具有遵循传统PRG方法的优势,有助于缓存中间人缓存结果等。
也就是说,无论如何,URI都是针对非ASCII的内容进行编码的,因此application / x-www-form-urlencoded和multipart / form-data也是如此。如果您打算支持ReSTful场景,我建议使用此方法,而不是创建另一种自定义json格式。
您可以发送带有主体的GET或发送POST并放弃RESTish宗教信仰(这还不错,五年前,只有一种信仰的人-他的评论在上面链接)。
好的决定都不是,但是发送GET正文可能会防止某些客户端和某些服务器出现问题。
在某些RESTish框架中进行POST可能会遇到障碍。
朱利安·雷施克(Julian Reschke)上面建议使用非标准的HTTP标头(例如“ SEARCH”),这可能是一个很好的解决方案,只是它受到支持的可能性更低。
列出可以完成上述每一项工作的客户端可能最有效率。
无法发送带有主体的GET的客户端(我知道):
可以发送带有正文的GET的客户端:
可以从GET检索正文的服务器和库:
从GET剥离主体的服务器(和代理):
SEARCH
方法可能会一路破坏吗?如果代理不理解方法,那么他们应该按原样通过它,所以我不太确定为什么您认为它会破坏任何东西……
哪个服务器会忽略它?– 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.
您还会从Bing,Apple等处收到400错误请求,这些请求由AkamaiGhost提供。
因此,我不建议将GET请求与主体实体一起使用。
GET
请求添加主体时,是因为他们自己的自定义服务器能够处理它。因此,问题在于其他“移动部件”(浏览器,缓存等)是否将正常运行。
GET
在该特定终结点上不希望(或明智)您的有效负载-与GET
一般情况下的使用无关。如果内容的格式在特定请求的上下文中不合理,则随机有效载荷可能会POST
轻易中断并返回相同400 Bad Request
的内容。
根据RFC 2616第4.3节 “消息正文”:
服务器应根据任何请求读取并转发消息正文;如果请求方法不包含为实体主体定义的语义,则在处理请求时应忽略消息主体。
也就是说,服务器应始终从网络读取任何提供的请求正文(检查Content-Length或读取分块正文等)。同样,代理应转发收到的任何此类请求正文。然后,如果RFC为给定方法定义了主体的语义,则服务器实际上可以使用请求主体来生成响应。但是,如果RFC 没有为主体定义语义,则服务器应忽略它。
这与上面Fielding的报价一致。
第9.3节 “ GET”描述了GET方法的语义,没有提及请求主体。因此,服务器应忽略它在GET请求上收到的任何请求正文。
根据XMLHttpRequest,它是无效的。从标准:
4.5.6
send()
方法client . send([body = null])
发起请求。可选参数提供请求正文。如果请求方法为
GET
或,则忽略该参数HEAD
。
InvalidStateError
如果未打开任何状态或send()
设置了标志,则引发异常 。该方法必须运行以下步骤:
send(body)
- 如果状态未打开,则引发
InvalidStateError
异常。- 如果
send()
设置了标志,则引发InvalidStateError
异常。- 如果request方法为
GET
或HEAD
,则将body设置为null。- 如果body为null,请转到下一步。
虽然,我认为不应该这样,因为GET请求可能需要大量内容。
因此,如果您依赖浏览器的XMLHttpRequest,则可能无法使用。
如果您确实想将可缓存的JSON / XML正文发送到Web应用程序,则放置数据的唯一合理位置是使用RFC4648编码的查询字符串:使用URL和Filename Safe Alphabet的Base 64编码。当然,您可以使用urlencode JSON并将其放在URL参数的值中,但是Base64给出的结果较小。请记住,存在URL大小限制,请参阅不同浏览器中URL的最大长度是多少?。
您可能会认为Base64的填充=
字符可能不利于URL的参数值,但事实并非如此-请参阅以下讨论:http : //mail.python.org/pipermail/python-bugs-list/2007-February/037195.html。但是,您不应将没有参数名称的编码数据放进去,因为带有填充的编码字符串将被解释为具有空值的参数键。我会用类似的东西?_b64=<encodeddata>
。
Vary
标头,我并不知道它的真正潜力。
我对REST感到不高兴,因为协议不支持OOP,并且Get
方法是证明。作为解决方案,您可以将DTO序列化为JSON,然后创建查询字符串。在服务器端,您可以将查询字符串反序列化为DTO。
看一下:
基于消息的方法可以帮助您解决Get方法的限制。您可以像请求正文一样发送任何DTO
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
例如,它可与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
通过POST请求和发送正文时读取application/x-www-form-urlencoded
。这意味着在GET
请求中将忽略主体。在这种情况下:$_GET
和$_POST
在这一点上都非常容易引起误解。更好地利用php://input
您有一个选项列表,它们比使用带有GET的请求正文要好得多。
假设您有类别和每个类别的项目。两者都由一个id标识(在此示例中为“ catid” /“ itemid”)。您要根据另一个参数“ sortby”以特定的“顺序”进行排序。您要传递“ sortby”和“ order”的参数:
您可以:
example.com/category/{catid}/item/{itemid}?sortby=itemname&order=asc
example.com/category/{catid}/item/{itemid}/{sortby}/{order}
都有缺点,但是比将GET与身体结合使用要好得多。
我在客户端程序中使用Spring框架的RestTemplate,在服务器端,我使用Json主体定义了GET请求。我的主要目的与您的主要目的相同:当请求具有多个参数时,将其放入正文中似乎比将其放入延长的URI字符串中更整齐。是?
但是,可悲的是,它不起作用!服务器端引发了以下异常:
org.springframework.http.converter.HttpMessageNotReadableException:缺少所需的请求正文...
但是我很确定消息代码是由我的客户端代码正确提供的,那么怎么了?
我追踪到RestTemplate.exchange()方法,发现以下内容:
// SimpleClientHttpRequestFactory.class
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
...
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
...
if (!"POST".equals(httpMethod) && !"PUT".equals(httpMethod) && !"PATCH".equals(httpMethod) && !"DELETE".equals(httpMethod)) {
connection.setDoOutput(false);
} else {
connection.setDoOutput(true);
}
...
}
}
// SimpleBufferingClientHttpRequest.class
final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest {
...
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
...
if (this.connection.getDoOutput() && this.outputStreaming) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
this.connection.connect();
if (this.connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
} else {
this.connection.getResponseCode();
}
...
}
}
请注意,在executeInternal()方法中,输入参数'bufferedOutput'包含我的代码提供的消息正文。我通过调试器看到了它。
但是,由于prepareConnection(),executeInternal()中的getDoOutput()始终返回false,这又使bufferedOutput完全被忽略!它不会复制到输出流中。
因此,我的服务器程序未收到消息正文,并引发了该异常。
这是有关Spring框架的RestTemplate的示例。关键是,即使HTTP规范不再禁止消息正文,某些客户端或服务器库或框架仍可能符合旧规范并拒绝GET请求中的消息正文。