【问题标题】:Does StringContent get disposed with HttpResponseMessage?StringContent 是否与 HttpResponseMessage 一起处理?
【发布时间】:2021-10-31 20:37:19
【问题描述】:

StringContent/HttpContentHttpRequestMessage 都是一次性的这些?例如

var content = new StringContent("test");

 using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri))
 {

        requestMessage.Content = content;

        var response = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
}

这需要是:

using(var content = new StringContent("test"))
{

     using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri))
     {
    
            requestMessage.Content = content;
    
            var response = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
    }
}

【问题讨论】:

  • 在阅读响应正文之前,您应该验证响应的 Content-Type 和状态码是否符合您的期望。
  • 是的,它只是一个简单粗略的例子,通常不会这样做
  • 您的代码处理HttpRequestMessage不是 HttpResponseMessage btw - 你的问题标题是HttpResponseMessage,但你的帖子的正文关注HttpRequestMessage

标签: c# asp.net .net asp.net-core .net-core


【解决方案1】:

(在撰写本文时,OP 的问题是模棱两可,他们指的是HttpRequestMessage 还是HttpResponseMessage,所以我将同时描述两者。

  • HttpRequestMessage.Dispose():
  • HttpResponseMessage.Dispose():
    • 处置其response.Content
    • 不会以任何其他方式处置response.RequestMessageHttpRequestMessage
      • 因此,它也不会处理 HttpRequestMessage.Content
    • See the source code here

HttpClient 完全发送完您的请求之前,您不应该处置HttpRequestMessage(我相信在某些(罕见的)情况下,您即使请求的内容还没有完成发送,也得到一个HttpResponseMessage 回复——比如在使用奇异的传输编码方案或多部分请求时)——但通常最好假设所有的嵌套生命周期涉及的对象,所以 _if System.Net.Http 是完美的(它不是)那么你会这样做:

  1. 确保请求/响应完全完成。
  2. 首先处理响应内容。
  3. 然后是HttpResponseMessage
  4. 然后是请求的内容。
  5. 然后是HttpRequestMessage
  6. 然后是HttpClient

...但是,当您处理父请求/响应消息时,处理内容是隐式的(并且您可能也不应该调用HttpClient.Dispose),所以只需这样做:

using (HttpClient httpClient = this.httpClientFactory.CreateHttpClient()) // Only dispose HttpClient instances created by IHttpClientFactory. *DO NOT DISPOSE* of other HttpClient instances unless you know what you're doing!
using (HttpContent reqContent = new StringContent("test")) // Disposing of this HttpContent is unnecessary (as it's disposed when `request` is disposed).
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri) { Content = reqContent })
using (HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false)) // Note there is no `using()` block for the HttpContent object, this is because we don't "own" the object, the HttpResponseMessage does.
{
    String responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    return responseBody;
}

尽管请记住 HttpRequestMessageHttpResponseMessage 对象是分开的,因此您可以处理您的 HttpRequestMessage 但仍然返回 HttpResponseMessage(反之亦然!)(尽管您不能 em> 如果您确实处理了HttpResponseMessage,则从ResponseMessage.Content.ReadAsStream() 返回Stream - 所以如果您需要返回Stream,您应该继承Stream 并包装返回的stream,但不要处理HttpResponseMessage 而是将其丢弃在您的 Stream 子类的 Dispose 方法中

public async Task<Stream> GetStreamAsync()
{
    using( HttpClient httpClient = this.httpClientFactory.CreateHttpClient() )
    using( HttpContent reqContent = new StringContent("test") )
    using( HttpRequestMessage request = new HttpRequestMessage( HttpMethod.Post, requestUri) { Content = reqContent })
    {
        HttpResponseMessage response = await this.httpClient.SendAsync( request ).ConfigureAwait(false);
        try
        {
            Stream stream = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            return new StreamWithDependencies( stream, response ); // See `class StreamWithDependencies` below.
        }
        catch // <-- When returning an IDisposable you should only dispose of things in `catch`, not in a `finally`.
        {
            response.Dispose(); // <-- Doing this means `response` (and `response.Content` will always be disposed) *and* can optionally outlive the method's scope if it follows the happy-path.
            throw; // <-- Don't swallow any exceptions, re-throw them (don't use `throw ex;` as that resets the StackTrace, always use either `throw;` (without a name) or throw a new exception with the caught exception passed as the `Exception.InnerException`.)
        }
    }
}

这是class Stream2 的示例:主要是它代理Stream,并且它的Dispose 方法将处理您告诉它的其他对象。

using System.Collections.Immutable;
using System.IO;

class StreamWithDependencies : Stream
{
    private readonly Stream subject;
    private readonly ImmutableList<IDisposable> disposeWith;

    public StreamWithDependencies( Stream subject, params IDisposable[] disposeWith )
    {
        this.subject = subject ?? throw new ArgumentNullException(nameof(subject));
        this.disposeWith = ( disposeWith ?? Array.Empty<IDisposable>() ).ToImmutableList();
    }

    protected override void Dispose(Boolean disposing)
    {
        if( disposing )
        {
            this.subject.Dispose();
            foreach( IDisposable d in this.disposeWith )
            {
                d.Dispose();
            }
        }

        base.Dispose(disposing);
    }

    public override Int32 Read(...)  => this.subject.Read( ... ):
    pubiic override Task<Int32> ReadAsync( ... ) => this.Subject.ReadAsync( ... );
    // etc
    // Only implement and forward methods for reading, no need to implement methods for writing.
}

【讨论】:

  • 如果请求自动处理 HttpContent/StringContent ,不应该在请求内容中显式添加using 吗?
  • @joe 正确。我这样做是出于视觉一致性的原因。但是,是的,它是可选的
  • @joe 排序,问题是你的代码(在那个例子中)没有处理HttpRequestMessage ctor 抛出的情况,在这种情况下@ 987654361@ 不会被释放(因为它没有附加到任何请求,并且因为失败的 ctor 中的异常不会导致调用 Dispose,所以它是全有或全无)。
  • “是否可以覆盖所有基础并安全地处理它需要在自己的using 中的内容?” - 是的,完全正确
  • 虽然记住 HttpRequestMessageHttpResponseMessage 对象是分开的,所以你可以处理你的 HttpRequestMessage 但仍然返回 HttpResponseMessage (反之亦然!)(虽然你 不能从ResponseMessage.Content.ReadAsStream()返回Stream如果你处理了HttpResponseMessage - 所以如果你需要返回一个Stream你应该继承Stream并包装返回的stream但是不要丢弃HttpResponseMessage,而是将它你的Stream子类的Dispose方法中丢弃。
猜你喜欢
  • 2015-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多