HttpClient.SendAsync 不发送请求正文
Posted
技术标签:
【中文标题】HttpClient.SendAsync 不发送请求正文【英文标题】:HttpClient.SendAsync not sending request body 【发布时间】:2014-10-03 14:08:23 【问题描述】:我正在使用 .NET 4.0 的 ASP.NET Web API 客户端库(Microsoft.AspNet.WebApi.Client 版本 4.0.30506.0)。
我需要发送带有请求正文的 HTTP DELETE。我将其编码如下:
using (var client = new HttpClient())
client.BaseAddress = Uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// I would normally use httpClient.DeleteAsync but I can't because I need to set content on the request.
// For this reason I use httpClient.SendAsync where I can both specify the HTTP DELETE with a request body.
var request = new HttpRequestMessage(HttpMethod.Delete, string.Format("myresource/0", sessionId))
var data = new Dictionary<string, object> "some-key", "some-value";
Content = new ObjectContent<IDictionary<string, object>>(data, new JsonMediaTypeFormatter())
;
var response = await client.SendAsync(request);
// code elided
对于每个 Fiddler,请求正文永远不会被序列化:
DELETE http://localhost:8888/myApp/sessions/blabla123 HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: localhost:8888
Content-Length: 38
Expect: 100-continue
来自服务器的响应:
HTTP/1.1 408 Request body incomplete
Date: Sun, 10 Aug 2014 17:55:17 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-cache, must-revalidate
Timestamp: 13:55:17.256
The request body did not contain the specified number of bytes. Got 0, expected 38
我尝试了许多变通方法,包括将要序列化的类型更改为其他类型、自己使用 JsonSerialize 进行序列化、将 HTTP DELETE 更改为 PUT 等...
没有任何效果。任何帮助将不胜感激。
【问题讨论】:
ObjectContent
类型是什么?
顺便说一句,您是否尝试过使用DeleteAsync
而不将内容类型设置为application-json
?
ObjectContent 泛型类型是 IDictionarynew ObjectContent<IDictionary<string, object>>(data, new JsonMediaTypeFormatter())
。我确实尝试了 StringContent ,但这也不起作用。当我序列化我的测试数据以放入字符串时,它看起来像这样 "some-key", "some-value"
【参考方案1】:
万一其他人遇到这种情况,我注意到可能导致这种情况的一件事是,如果您在标题中设置了换行符。
我们有一个加密的 OAuth 令牌,它在运行时被解密并设置为应用程序的 OAuth 标头。换行符被加密到令牌中,因此从查看配置或那里的任何内容来看并不明显,但如果你这样做:
var message = new HttpRequestMessage(HttpMethod.Post, "https://example.com");
message.Headers.ContentType = new MediaTypeHeaderValue("application/json");
message.Content = new StringContent(" \"someKey\": \"someValue\" ", Encoding.UTF8);
// note the trailing newline
message.Headers.Authorization = new AuthenticationHeaderValue("OAuth", "my auth token\n");
var response = await httpClient.SendAsync(request);
HTTP 请求将被发送,但内容不会随之发送。发生这种情况时不会引发异常,如果您检查 HttpRequestMessage,内容似乎就在那里,但实际上并没有通过网络发送。
这发生在 Windows 和 Linux 上的 .NET 5 中,我尚未在其他框架版本/平台上对其进行测试。
【讨论】:
【参考方案2】:我解决了这个问题,尽管它没有意义。我注意到,如果我将调用更改为 HTTP PUT 或 POST,它仍然无法将 Content 序列化为请求正文。这很奇怪,因为以前的 PUT 和 POST 都是成功的。在对框架库进行了大量调试(使用反射器)之后,我终于找到了唯一剩下的“不同”。
我正在使用 NUnit 2.6.2。我的测试结构是:
[Test]
async public void Test()
// successful HTTP POST and PUT calls here
// successful HTTP DELETE with request body here (after
// moving it from the TearDown below)
[TearDown]
async public void TerminateSession()
// failed HTTP DELETE with request body here
为什么这在 TearDown 中失败,但在测试本身中却没有?我不知道。 TearDown 属性或使用 async 关键字(因为我等待异步调用)是否发生了什么?
我不确定是什么导致了这种行为,但我现在知道我可以提交带有请求正文的 HTTP DELETE(如问题中的代码示例中所述)。
另一个可行的解决方案如下:
[Test]
async public void Test()
// create and use an HttpClient here, doing POSTs, PUTs, and GETs
// Notice the removal of the async keyword since now using Wait() in method body
[TearDown]
public void TerminateSession()
// create and use an HttpClient here and use Wait().
httpClient.SendAsync(httpRequestMessage).Wait();
【讨论】:
【参考方案3】:我知道说“不要那样做”从来没有那么有帮助,但在这种情况下,我认为将调用拆分为 DELETE 之后或之前是 POST 或 PUT 是有意义的。
HTTP RFC 并未明确就此事发表意见,因此从技术上讲,这意味着我们可以。然而,另一个问题是应该我们这样做。
在这种情况下,我会寻找其他实现以了解 de facto 标准是什么。正如您在 .net 实现中发现的那样,设计人员似乎没想到会发送带有 DELETE 调用的正文。那么,让我们看看另一个流行的(和非常不同的 impl)Python Requests:
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
这里没有其他人。因此,如果规范作者没有提及它,而流行的实现假设没有主体,那么最小意外原则意味着我们也不应该这样做。
因此,如果您可以更改 API,API 的客户端将更容易拆分为两个调用。否则,您可能不得不求助于自定义骇客将正文塞进 DELETE 调用中。
好消息是您可能在 .net 框架中发现了一个错误,这本身就是一项成就。广告非零内容长度但未实际发送的客户端已损坏。
【讨论】:
同意,该规范可以解释。并且在某处有一个错误,尽管它可能在 NUnit 中。以上是关于HttpClient.SendAsync 不发送请求正文的主要内容,如果未能解决你的问题,请参考以下文章
C# HttpClient.SendAsync 在测试某些 URL 时抛出“发送请求时发生错误”异常
在 HttpClient.SendAsync() 之后无法访问已释放的对象
模拟 HttpClient.SendAsync 以返回内容不为空的响应