diff --git a/src/RestSharp/Response/ResponseHandling.cs b/src/RestSharp/Response/ResponseHandling.cs index deeee6a89..abc983b99 100644 --- a/src/RestSharp/Response/ResponseHandling.cs +++ b/src/RestSharp/Response/ResponseHandling.cs @@ -19,7 +19,7 @@ namespace RestSharp; static class ResponseHandling { public static string GetResponseString(this HttpResponseMessage response, byte[] bytes) { - var encodingString = response.Content.Headers.ContentEncoding.FirstOrDefault(); + var encodingString = response.Content.Headers.ContentType?.CharSet; var encoding = encodingString != null ? TryGetEncoding(encodingString) : Encoding.Default; return encoding.GetString(bytes); @@ -37,7 +37,7 @@ Encoding TryGetEncoding(string es) { #if NETSTANDARD return response.Content.ReadAsStreamAsync(); # else - return response.Content.ReadAsStreamAsync(cancellationToken); + return response.Content.ReadAsStreamAsync(cancellationToken)!; #endif } } \ No newline at end of file diff --git a/src/RestSharp/Response/RestResponse.cs b/src/RestSharp/Response/RestResponse.cs index 0c58a87ff..8647780ee 100644 --- a/src/RestSharp/Response/RestResponse.cs +++ b/src/RestSharp/Response/RestResponse.cs @@ -69,8 +69,9 @@ CancellationToken cancellationToken async Task GetDefaultResponse() { var readTask = request.ResponseWriter == null ? ReadResponse() : ReadAndConvertResponse(); using var stream = await readTask.ConfigureAwait(false); - var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false); - var content = bytes == null ? null : httpResponse.GetResponseString(bytes); + + var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false); + var content = bytes == null ? null : httpResponse.GetResponseString(bytes); return new RestResponse { Content = content, @@ -80,6 +81,7 @@ async Task GetDefaultResponse() { ContentLength = httpResponse.Content.Headers.ContentLength, ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ResponseStatus = httpResponse.IsSuccessStatusCode ? ResponseStatus.Completed : ResponseStatus.Error, + ErrorException = MaybeException(), ResponseUri = httpResponse.RequestMessage!.RequestUri, Server = httpResponse.Headers.Server.ToString(), StatusCode = httpResponse.StatusCode, @@ -91,6 +93,11 @@ async Task GetDefaultResponse() { Cookies = cookieCollection }; + Exception? MaybeException() + => httpResponse.IsSuccessStatusCode + ? null + : new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}"); + Task ReadResponse() => httpResponse.ReadResponse(cancellationToken); async Task ReadAndConvertResponse() { diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index b136f0fd5..2ae8af5c6 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -35,11 +35,13 @@ public async Task ExecuteAsync(RestRequest request, CancellationTo cancellationToken ) .ConfigureAwait(false) - : ReturnErrorOrThrow(response, internalResponse.Exception, internalResponse.TimeoutToken); + : AddError(response, internalResponse.Exception, internalResponse.TimeoutToken); + response.Request = request; response.Request.IncreaseNumAttempts(); - return response; + + return Options.ThrowOnAnyError ? ThrowIfError(response) : response; } async Task ExecuteInternal(RestRequest request, CancellationToken cancellationToken) { @@ -120,18 +122,15 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception return stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false); } - RestResponse ReturnErrorOrThrow(RestResponse response, Exception exception, CancellationToken timeoutToken) { - if (exception is OperationCanceledException) { - response.ResponseStatus = timeoutToken.IsCancellationRequested ? ResponseStatus.TimedOut : ResponseStatus.Aborted; - } - else { - response.ResponseStatus = ResponseStatus.Error; - } + static RestResponse AddError(RestResponse response, Exception exception, CancellationToken timeoutToken) { + response.ResponseStatus = exception is OperationCanceledException + ? timeoutToken.IsCancellationRequested ? ResponseStatus.TimedOut : ResponseStatus.Aborted + : ResponseStatus.Error; response.ErrorMessage = exception.Message; response.ErrorException = exception; - return Options.ThrowOnAnyError ? ThrowIfError(response) : response; + return response; } static RestResponse ThrowIfError(RestResponse response) { diff --git a/test/RestSharp.IntegrationTests/RequestFailureTests.cs b/test/RestSharp.IntegrationTests/RequestFailureTests.cs new file mode 100644 index 000000000..5493d135e --- /dev/null +++ b/test/RestSharp.IntegrationTests/RequestFailureTests.cs @@ -0,0 +1,61 @@ +using System.Net; +using RestSharp.IntegrationTests.Fixtures; + +namespace RestSharp.IntegrationTests; + +[Collection(nameof(TestServerCollection))] +public class RequestFailureTests { + readonly RestClient _client; + readonly TestServerFixture _fixture; + + public RequestFailureTests(TestServerFixture fixture) { + _client = new RestClient(fixture.Server.Url); + _fixture = fixture; + } + + [Fact] + public async Task Handles_GET_Request_Errors() { + var request = new RestRequest("status?code=404"); + var response = await _client.ExecuteAsync(request); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Handles_GET_Request_Errors_With_Response_Type() { + var request = new RestRequest("status?code=404"); + var response = await _client.ExecuteAsync(request); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + response.Data.Should().Be(null); + } + + [Fact] + public async Task Throws_on_unsuccessful_call() { + var client = new RestClient(new RestClientOptions(_fixture.Server.Url) { ThrowOnAnyError = true }); + var request = new RestRequest("status?code=404"); + + var task = () => client.ExecuteAsync(request); + await task.Should().ThrowExactlyAsync(); + } + + [Fact] + public async Task GetAsync_throws_on_unsuccessful_call() { + var request = new RestRequest("status?code=404"); + + var task = () => _client.GetAsync(request); + await task.Should().ThrowExactlyAsync(); + } + + [Fact] + public async Task GetAsync_generic_throws_on_unsuccessful_call() { + var request = new RestRequest("status?code=404"); + + var task = () => _client.GetAsync(request); + await task.Should().ThrowExactlyAsync(); + } + + class Response { + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/test/RestSharp.IntegrationTests/AsyncTests.cs b/test/RestSharp.IntegrationTests/RequestTests.cs similarity index 76% rename from test/RestSharp.IntegrationTests/AsyncTests.cs rename to test/RestSharp.IntegrationTests/RequestTests.cs index 552bfeb4e..f3212e3d8 100644 --- a/test/RestSharp.IntegrationTests/AsyncTests.cs +++ b/test/RestSharp.IntegrationTests/RequestTests.cs @@ -61,21 +61,4 @@ public async Task Can_Timeout_GET_Async() { Assert.Equal(ResponseStatus.TimedOut, response.ResponseStatus); } - - [Fact] - public async Task Handles_GET_Request_Errors_Async() { - var request = new RestRequest("status?code=404"); - var response = await _client.ExecuteAsync(request); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - public async Task Handles_GET_Request_Errors_Async_With_Response_Type() { - var request = new RestRequest("status?code=404"); - var response = await _client.ExecuteAsync(request); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - Assert.Null(response.Data); - } } \ No newline at end of file