HttpClient disposed before response stream fully read, leads to connection closed exception

Viewed 536

I'm trying to stream the response of a request to the response. So basically pipe through the request to another url. This is the code I have, which illustrates what I'm trying to do.

public async Task<IActionResult> GetArticleImage(string userId, string articleId)
{
    var imageUrl = "https://image-url"; // This url is retrieved from the database
    var imageResult = await this.httpClient.GetAsync(imageUrl);
    var stream = await imageResult.Content.ReadAsStreamAsync();

    return new FileStreamResult(stream, imageResult.Content.Headers.GetValues("Content-Type").First());
}

What happens in this case is that a ConnectionResetException is thrown, with the message The client has disconnected.

I believe this happens because the request scope is disposed before the stream is fully read, which also disposes the HttpClient.
When a custom scope is created using var scope = IServiceScopeFactory.CreateScope() (pseudo code, actually an IServiceScopeFactory serviceScopeFactory is injected via the constructor), the HttpClient resolved via that one, and not disposed (neither scope nor client), it works without issue.

What is important to mention is that the method GetArticleImage is implemented in a separate service class, not in a controller.
Previously it was implemented in a controller class, where the code worked flawlessly.
This leads me to the assumption that the scope dispose mechanism works somehow slightly differently, but I didn't find out how.

The HttpClient is registered as singleton, which should make sure it's not disposed of.

The question is: How can a request be piped/streamed to another url?

The endpoint that calls this method can be called quite often, with many simultaneous requests, so I'd like to avoid having to cache the response in memory only to pass it along.

1 Answers

Sometimes it happens when external service works incorrect with .Net async/await

I found workaround(which is not best practice) But it worked for me.

public Task<IActionResult> GetArticleImage(string userId, string articleId)
{
    var imageUrl = "https://image-url"; // This url is retrieved from the database
    var imageResult = this.httpClient.GetAsync(imageUrl).GetAwaiter().GetResult();
    var stream = imageResult.Content.ReadAsStreamAsync().GetAwaiter().GetResult();

    return new FileStreamResult(stream, imageResult.Content.Headers.GetValues("Content-Type").First());
}
Related