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 泛型类型是 IDictionary。本质上是一组键值对。 DeleteAsync 不支持传递任何内容。 我将 HttpRequestMessage 的 Content 属性设置为 new ObjectContent&lt;IDictionary&lt;string, object&gt;&gt;(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 阻塞主线程

模拟 HttpClient.SendAsync 以返回内容不为空的响应

HttpClient.SendAsync 方法退出而不抛出异常

HttpClient SendAsync