HttpClient.SendAsync不发送请求体

本文关键字:请求 SendAsync HttpClient | 更新日期: 2023-09-27 18:17:25

我正在使用ASP。.NET的。NET Web API客户端库。客户端版本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等…

毫无效果。

HttpClient.SendAsync不发送请求体

我解决了这个问题,尽管它没有意义。我注意到,如果我更改对HTTP PUT或POST的调用,它仍然无法将Content序列化为请求体。这很奇怪,因为之前的put和post都是成功的。在对框架库进行了大量调试(使用Reflector)之后,我终于找到了唯一剩下的"不同"之处。"

我使用的是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中失败,而在Test本身中没有失败?我不知道。是否有事情发生与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();
}

我知道说"不要那样做"从来没有那么有帮助,但在这种情况下,我认为将调用拆分为DELETE之后或之前的POST或PUT是有意义的。

HTTP RFC没有明确地对这个问题发表意见,所以从技术上讲,这意味着我们可以。然而,另一个问题是我们是否应该这样做。

在这种情况下,我会寻找其他实现,看看什么是事实上的标准。正如您在.net实现中发现的那样,设计人员似乎并没有期望发送带有DELETE调用的体。那么,让我们看看另一个流行的(非常不同的impl) Python请求:
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'

这里没有其他人。因此,如果规范作者没有提到它,而流行的实现假设没有主体,那么最小意外原则意味着我们也不应该这样做。

因此,如果您可以更改API,则API的客户端将更容易分割为两个调用。否则,您可能不得不诉诸自定义hack,将body塞进DELETE调用中。

好消息是你很可能在。net框架中发现了一个bug,这本身就是一个成就。客户端广告一个非零的Content-Length而没有实际发送它是坏的。

以防其他人遇到这种情况,我注意到的一件事可能会导致这种情况,如果你设置一个标题中有换行符

我们有一个加密的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,内容将出现在那里,但实际上并没有通过网络发送。

这发生在。net 5在Windows和Linux上,我还没有在其他框架版本/平台上测试过。