Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't read HttpResponseMessage content when the status code is not success

I have a service which is consuming an SMS REST API using HttpClient:

HttpClient http = this._httpClientFactory.CreateClient();
// Skipped: setup HttpRequestMessage
using (HttpResponseMessage response = await http.SendAsync(request))
{
    try
    {
        _ = response.EnsureSuccessStatusCode();
    }
    catch (HttpRequestException)
    {
        string responseString = await response.Content.ReadAsStringAsync(); // Fails with ObjectDisposedException
        this._logger.LogInformation(
            "Received invalid HTTP response status '{0}' from SMS API. Response content was {1}.",
            (int)response.StatusCode,
            responseString
        );
        throw;
    }
}

The API returns an error, but I would like to be able to log it. So I need to log both the failing status code (which I can read from response.StatusCode) and the associated content (which may contain additional error useful details).

This code fails on the instruction await response.Content.ReadAsStringAsync() with this exception:

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Http.HttpConnection+HttpConnectionResponseContent'.
    Module "System.Net.Http.HttpContent", in CheckDisposed
    Module "System.Net.Http.HttpContent", in ReadAsStringAsync

Some sources suggest that you shouldn't read the response content when the status code is not in the success range (200-299), but what if the response really contains useful error details?

.NET version used: .NET Core 2.1.12 on AWS lambda linux runtime.

like image 481
Maxime Rossini Avatar asked Jan 10 '20 09:01

Maxime Rossini


People also ask

How do I set HttpResponseMessage content?

Several months ago, Microsoft decided to change up the HttpResponseMessage class. Before, you could simply pass a data type into the constructor, and then return the message with that data, but not anymore. Now, you need to use the Content property to set the content of the message.

What is HttpResponseMessage in C#?

A HttpResponseMessage allows us to work with the HTTP protocol (for example, with the headers property) and unifies our return type. In simple words an HttpResponseMessage is a way of returning a message/data from your action.

What is HTTP request exception?

HttpRequestException(String, Exception) Initializes a new instance of the HttpRequestException class with a specific message that describes the current exception and an inner exception. HttpRequestException(String, Exception, Nullable<HttpStatusCode>)


1 Answers

OK, apparently this is a known issue in the .NET API, which has been addressed in .NET Core 3.0. response.EnsureSuccessStatusCode() is actually disposing the response content. It was implemented this way to supposedly help users:

// Disposing the content should help users: If users call EnsureSuccessStatusCode(), an exception is
// thrown if the response status code is != 2xx. I.e. the behavior is similar to a failed request (e.g.
// connection failure). Users don't expect to dispose the content in this case: If an exception is
// thrown, the object is responsible fore cleaning up its state.

This is an undesirable behavior which was removed from 3.0. In the meantime, I just switched to use IsSuccessStatusCode before the log:

HttpClient http = this._httpClientFactory.CreateClient();
// Skipped: setup HttpRequestMessage
using (HttpResponseMessage response = await http.SendAsync(request))
{
    if (!response.IsSuccessStatusCode)
    {
        string responseString = await response.Content.ReadAsStringAsync(); // Fails with ObjectDisposedException
        this._logger.LogInformation(
            "Received invalid HTTP response status '{0}' from SMS API. Response content was {1}.",
            (int)response.StatusCode,
            responseString
        );
        _ = response.EnsureSuccessStatusCode();
    }
}

A little bit more redundant, but it should work.

like image 97
Maxime Rossini Avatar answered Sep 23 '22 13:09

Maxime Rossini