diff --git a/docs/features/errorcodes.rst b/docs/features/errorcodes.rst new file mode 100644 index 00000000..fe058790 --- /dev/null +++ b/docs/features/errorcodes.rst @@ -0,0 +1,13 @@ +Http Error Status Codes +======================= + +Ocelot will return HTTP status error codes based on internal logic in certain siturations: +- 401 if the authentication middleware runs and the user is not authenticated. +- 403 if the authorisation middleware runs and the user is unauthenticated, claim value not authroised, scope not authorised, user doesnt have required claim or cannot find claim. +- 503 if the downstream request times out. +- 499 if the request is cancelled by the client. +- 404 if unable to find a downstream route. +- 502 if unable to connect to downstream service. +- 500 if unable to complete the HTTP request downstream and the exception is not OperationCanceledException or HttpRequestException. +- 404 if Ocelot is unable to map an internal error code to a HTTP status code. + diff --git a/docs/index.rst b/docs/index.rst index 7952c91a..1acbd712 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,6 +42,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/loadbalancer features/delegatinghandlers features/raft + features/errorcodes .. toctree:: :maxdepth: 2 diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index d8b6f333..6ca67e7d 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -39,6 +39,7 @@ CannotAddPlaceholderError = 34, CannotRemovePlaceholderError = 35, QuotaExceededError = 36, - RequestCanceled = 37, + RequestCanceled = 37, + ConnectionToDownstreamServiceError = 38, } } diff --git a/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs b/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs new file mode 100644 index 00000000..6c03863c --- /dev/null +++ b/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs @@ -0,0 +1,13 @@ +using Ocelot.Errors; +using System; + +namespace Ocelot.Requester +{ + public class ConnectionToDownstreamServiceError : Error + { + public ConnectionToDownstreamServiceError(Exception exception) + : base($"Error connecting to downstream service, exception: {exception}", OcelotErrorCode.ConnectionToDownstreamServiceError) + { + } + } +} diff --git a/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs b/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs index f2fbad2d..a86fcf37 100644 --- a/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs +++ b/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs @@ -4,6 +4,7 @@ namespace Ocelot.Requester using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; + using System.Net.Http; public class HttpExeptionToErrorMapper : IExceptionToErrorMapper { @@ -28,6 +29,11 @@ namespace Ocelot.Requester return new RequestCanceledError(exception.Message); } + if (type == typeof(HttpRequestException)) + { + return new ConnectionToDownstreamServiceError(exception); + } + return new UnableToCompleteRequestError(exception); } } diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index c50daa27..6b0ee6cc 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -38,6 +38,11 @@ namespace Ocelot.Responder if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError)) { return 404; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.ConnectionToDownstreamServiceError)) + { + return 502; } if (errors.Any(e => e.Code == OcelotErrorCode.UnableToCompleteRequestError)) diff --git a/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs index 26da864f..be1b2f66 100644 --- a/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs @@ -1,8 +1,9 @@ -using Ocelot.Errors; -using System.Collections.Generic; +namespace Ocelot.Responder +{ + using System.Net; + using Ocelot.Errors; + using System.Collections.Generic; -namespace Ocelot.Responder -{ /// /// Map a list OceoltErrors to a single appropriate HTTP status code /// diff --git a/test/Ocelot.AcceptanceTests/HttpTests.cs b/test/Ocelot.AcceptanceTests/HttpTests.cs index 57947d07..da59d000 100644 --- a/test/Ocelot.AcceptanceTests/HttpTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpTests.cs @@ -141,7 +141,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_return_response_500_when_using_http_one_to_talk_to_server_running_http_two() + public void should_return_response_502_when_using_http_one_to_talk_to_server_running_http_two() { var port = RandomPortFinder.GetRandomPort(); @@ -177,7 +177,7 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/", httpContent)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.BadGateway)) .BDDfy(); } diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 1ebc1113..323c9e14 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -16,6 +16,38 @@ { _serviceHandler = new ServiceHandler(); _steps = new Steps(); + } + + [Fact] + public void should_return_bad_gateway_error_if_downstream_service_doesnt_respond() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53877, + }, + }, + DownstreamScheme = "http", + }, + }, + }; + + this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.BadGateway)) + .BDDfy(); } [Fact] diff --git a/test/Ocelot.AcceptanceTests/SslTests.cs b/test/Ocelot.AcceptanceTests/SslTests.cs index 58c9e6a1..ee1f8141 100644 --- a/test/Ocelot.AcceptanceTests/SslTests.cs +++ b/test/Ocelot.AcceptanceTests/SslTests.cs @@ -89,7 +89,7 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.BadGateway)) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs b/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs index 14413aad..a05ccec0 100644 --- a/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs @@ -7,6 +7,7 @@ using Shouldly; using System; using System.Collections.Generic; + using System.Net.Http; using System.Threading.Tasks; using Xunit; @@ -38,6 +39,14 @@ error.ShouldBeOfType(); } + [Fact] + public void should_return_ConnectionToDownstreamServiceError() + { + var error = _mapper.Map(new HttpRequestException()); + + error.ShouldBeOfType(); + } + [Fact] public void should_return_request_canceled_for_subtype() { diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 8b550a05..d662f86f 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -50,6 +50,13 @@ namespace Ocelot.UnitTests.Responder public void should_return_internal_server_error(OcelotErrorCode errorCode) { ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.InternalServerError); + } + + [Theory] + [InlineData(OcelotErrorCode.ConnectionToDownstreamServiceError)] + public void should_return_bad_gateway_error(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.BadGateway); } [Theory] @@ -125,7 +132,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(38, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(39, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)