Feature/inject error mapper (#562)

* added delegate to select last handler

* #529 implemented a way we can inject the last delegating handler

* wip - moving code

* #529 removed loads of qos code and moved it into Ocelot.Provider.Polly

* #529 can now inject http client expcetions to ocelot errors mappers and updated docs
This commit is contained in:
Tom Pallister 2018-08-19 12:57:43 +01:00 committed by GitHub
parent 98ba0271be
commit 6d8b18c01d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 177 additions and 317 deletions

View File

@ -5,7 +5,22 @@ Ocelot supports one QoS capability at the current time. You can set on a per ReR
want to use a circuit breaker when making requests to a downstream service. This uses the an awesome
.NET library called Polly check them out `here <https://github.com/App-vNext/Polly>`_.
Add the following section to a ReRoute configuration.
The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package..
``Install-Package Ocelot.Provider.Polly``
Then in your ConfigureServices method
.. code-block:: csharp
public virtual void ConfigureServices(IServiceCollection services)
{
services
.AddOcelot()
.AddPolly();
}
Then add the following section to a ReRoute configuration.
.. code-block:: json

View File

@ -7,8 +7,6 @@
int durationofBreak,
int timeoutValue,
string key,
//todo - this is never set in Ocelot so always Pessimistic...I guess it doesn't
//matter to much.
string timeoutStrategy = "Pessimistic")
{
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;

View File

@ -130,6 +130,7 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
Services.TryAddSingleton<IQoSFactory, QoSFactory>();
Services.TryAddSingleton<IExceptionToErrorMapper, HttpExeptionToErrorMapper>();
}
public IOcelotBuilder AddSingletonDefinedAggregator<T>()

View File

@ -12,14 +12,17 @@ namespace Ocelot.Requester
private readonly IHttpClientCache _cacheHandlers;
private readonly IOcelotLogger _logger;
private readonly IDelegatingHandlerHandlerFactory _factory;
private readonly IExceptionToErrorMapper _mapper;
public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory,
IHttpClientCache cacheHandlers,
IDelegatingHandlerHandlerFactory house)
IDelegatingHandlerHandlerFactory factory,
IExceptionToErrorMapper mapper)
{
_logger = loggerFactory.CreateLogger<HttpClientHttpRequester>();
_cacheHandlers = cacheHandlers;
_factory = house;
_factory = factory;
_mapper = mapper;
}
public async Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext context)
@ -35,7 +38,8 @@ namespace Ocelot.Requester
}
catch (Exception exception)
{
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
var error = _mapper.Map(exception);
return new ErrorResponse<HttpResponseMessage>(error);
}
finally
{

View File

@ -0,0 +1,29 @@
namespace Ocelot.Requester
{
using System;
using System.Collections.Generic;
using Errors;
using Microsoft.Extensions.DependencyInjection;
public class HttpExeptionToErrorMapper : IExceptionToErrorMapper
{
private readonly Dictionary<Type, Func<Exception, Error>> _mappers;
public HttpExeptionToErrorMapper(IServiceProvider serviceProvider)
{
_mappers = serviceProvider.GetService<Dictionary<Type, Func<Exception, Error>>>();
}
public Error Map(Exception exception)
{
var type = exception.GetType();
if (_mappers != null && _mappers.ContainsKey(type))
{
return _mappers[type](exception);
}
return new UnableToCompleteRequestError(exception);
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using Ocelot.Errors;
namespace Ocelot.Requester
{
public interface IExceptionToErrorMapper
{
Error Map(Exception exception);
}
}

View File

@ -1,10 +0,0 @@
using System.Net.Http;
namespace Ocelot.Requester
{
public class ReRouteDelegatingHandler<T>
where T : DelegatingHandler
{
public T DelegatingHandler { get; private set; }
}
}

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.Requester
{
public class UnableToFindDelegatingHandlerProviderError : Error
{
public UnableToFindDelegatingHandlerProviderError(string message)
: base(message, OcelotErrorCode.UnableToFindDelegatingHandlerProviderError)
{
}
}
}

View File

@ -27,6 +27,7 @@ namespace Ocelot.UnitTests.Requester
private DownstreamContext _request;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
private Mock<IExceptionToErrorMapper> _mapper;
public HttpClientHttpRequesterTest()
{
@ -38,10 +39,12 @@ namespace Ocelot.UnitTests.Requester
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
.Returns(_logger.Object);
_cacheHandlers = new Mock<IHttpClientCache>();
_mapper = new Mock<IExceptionToErrorMapper>();
_httpClientRequester = new HttpClientHttpRequester(
_loggerFactory.Object,
_cacheHandlers.Object,
_factory.Object);
_factory.Object,
_mapper.Object);
}
[Fact]
@ -144,6 +147,7 @@ namespace Ocelot.UnitTests.Requester
private void ThenTheErrorIsTimeout()
{
_mapper.Verify(x => x.Map(It.IsAny<Exception>()), Times.Once);
_response.Errors[0].ShouldBeOfType<UnableToCompleteRequestError>();
}
@ -165,6 +169,8 @@ namespace Ocelot.UnitTests.Requester
};
_factory.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())).Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
_mapper.Setup(x => x.Map(It.IsAny<Exception>())).Returns(new UnableToCompleteRequestError(new Exception()));
}
class OkDelegatingHandler : DelegatingHandler

View File

@ -0,0 +1,52 @@
namespace Ocelot.UnitTests.Requester
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Errors;
using Ocelot.Requester;
using Responder;
using Shouldly;
using Xunit;
public class HttpExeptionToErrorMapperTests
{
private HttpExeptionToErrorMapper _mapper;
private readonly ServiceCollection _services;
public HttpExeptionToErrorMapperTests()
{
_services = new ServiceCollection();
var provider = _services.BuildServiceProvider();
_mapper = new HttpExeptionToErrorMapper(provider);
}
[Fact]
public void should_return_default_error_because_mappers_are_null()
{
var error = _mapper.Map(new Exception());
error.ShouldBeOfType<UnableToCompleteRequestError>();
}
[Fact]
public void should_return_error_from_mapper()
{
var errorMapping = new Dictionary<Type, Func<Exception, Error>>
{
{typeof(TaskCanceledException), e => new AnyError()},
};
_services.AddSingleton(errorMapping);
var provider = _services.BuildServiceProvider();
_mapper = new HttpExeptionToErrorMapper(provider);
var error = _mapper.Map(new TaskCanceledException());
error.ShouldBeOfType<AnyError>();
}
}
}

View File

@ -0,0 +1,55 @@
namespace Ocelot.UnitTests.Requester
{
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Logging;
using Ocelot.Requester;
using Ocelot.Requester.QoS;
using Shouldly;
using Xunit;
public class QoSFactoryTests
{
private QoSFactory _factory;
private ServiceCollection _services;
private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
public QoSFactoryTests()
{
_services = new ServiceCollection();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
var provider = _services.BuildServiceProvider();
_factory = new QoSFactory(provider, _loggerFactory.Object);
}
[Fact]
public void should_return_error()
{
var downstreamReRoute = new DownstreamReRouteBuilder().Build();
var handler = _factory.Get(downstreamReRoute);
handler.IsError.ShouldBeTrue();
handler.Errors[0].ShouldBeOfType<UnableToFindQoSProviderError>();
}
[Fact]
public void should_return_handler()
{
_services = new ServiceCollection();
DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute a, IOcelotLoggerFactory b) => new FakeDelegatingHandler();
_services.AddSingleton<QosDelegatingHandlerDelegate>(QosDelegatingHandlerDelegate);
var provider = _services.BuildServiceProvider();
_factory = new QoSFactory(provider, _loggerFactory.Object);
var downstreamReRoute = new DownstreamReRouteBuilder().Build();
var handler = _factory.Get(downstreamReRoute);
handler.IsError.ShouldBeFalse();
handler.Data.ShouldBeOfType<FakeDelegatingHandler>();
}
class FakeDelegatingHandler : DelegatingHandler
{
}
}
}

View File

@ -1,93 +0,0 @@
/*
namespace Ocelot.UnitTests.Requester
{
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Shouldly;
using System.Collections.Generic;
using TestStack.BDDfy;
using Xunit;
public class QoSProviderFactoryTests
{
private readonly IQoSProviderFactory _factory;
private DownstreamReRoute _reRoute;
private IQoSProvider _result;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
public QoSProviderFactoryTests()
{
_logger = new Mock<IOcelotLogger>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
_factory = new QoSProviderFactory(_loggerFactory.Object, provider);
}
[Fact]
public void should_return_no_qos_provider()
{
var qosOptions = new QoSOptionsBuilder()
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithQosOptions(qosOptions)
.WithUpstreamHttpMethod(new List<string> { "get" })
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.When(x => x.WhenIGetTheQoSProvider())
.Then(x => x.ThenTheQoSProviderIsReturned<NoQoSProvider>())
.BDDfy();
}
[Fact]
public void should_return_delegate_provider()
{
var qosOptions = new QoSOptionsBuilder()
.WithTimeoutValue(100)
.WithDurationOfBreak(100)
.WithExceptionsAllowedBeforeBreaking(100)
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithUpstreamHttpMethod(new List<string> { "get" })
.WithQosOptions(qosOptions)
.Build();
this.Given(x => x.GivenAReRoute(reRoute))
.When(x => x.WhenIGetTheQoSProvider())
.Then(x => x.ThenTheQoSProviderIsReturned<FakeProvider>())
.BDDfy();
}
private void GivenAReRoute(DownstreamReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenIGetTheQoSProvider()
{
_result = _factory.Get(_reRoute);
}
private void ThenTheQoSProviderIsReturned<T>()
{
_result.ShouldBeOfType<T>();
}
}
internal class FakeProvider : IQoSProvider
{
public T CircuitBreaker<T>()
{
throw new System.NotImplementedException();
}
}
}
*/

View File

@ -1,195 +0,0 @@
/*
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class QosProviderHouseTests
{
private IQoSProvider _qoSProvider;
private readonly QosProviderHouse _qosProviderHouse;
private Response<IQoSProvider> _getResult;
private DownstreamReRoute _reRoute;
private readonly Mock<IQoSProviderFactory> _factory;
public QosProviderHouseTests()
{
_factory = new Mock<IQoSProviderFactory>();
_qosProviderHouse = new QosProviderHouse(_factory.Object);
}
[Fact]
public void should_store_qos_provider_on_first_request()
{
var qosOptions = new QoSOptionsBuilder()
.WithKey("test")
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithQosOptions(qosOptions)
.Build();
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
.Then(x => x.ThenItIsAdded())
.BDDfy();
}
[Fact]
public void should_not_store_qos_provider_on_first_request()
{
var qosOptions = new QoSOptionsBuilder()
.WithKey("test")
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithQosOptions(qosOptions)
.Build();
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
.Then(x => x.ThenItIsReturned())
.BDDfy();
}
[Fact]
public void should_store_qos_providers_by_key()
{
var qosOptions = new QoSOptionsBuilder()
.WithKey("test")
.Build();
var qosOptionsTwo = new QoSOptionsBuilder()
.WithKey("testTwo")
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithQosOptions(qosOptions)
.Build();
var reRouteTwo = new DownstreamReRouteBuilder()
.WithQosOptions(qosOptionsTwo)
.Build();
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
.And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
.Then(x => x.ThenTheQoSProviderIs<FakeQoSProvider>())
.When(x => x.WhenWeGetTheQoSProvider(reRouteTwo))
.Then(x => x.ThenTheQoSProviderIs<FakePollyQoSProvider>())
.BDDfy();
}
[Fact]
public void should_return_error_if_no_qos_provider_with_key()
{
var qosOptions = new QoSOptionsBuilder()
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithQosOptions(qosOptions)
.Build();
this.When(x => x.WhenWeGetTheQoSProvider(reRoute))
.Then(x => x.ThenAnErrorIsReturned())
.BDDfy();
}
[Fact]
public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed()
{
var useQoSOptions = new QoSOptionsBuilder()
.WithTimeoutValue(1)
.WithKey("test")
.WithDurationOfBreak(1)
.WithExceptionsAllowedBeforeBreaking(1)
.Build();
var dontUseQoSOptions = new QoSOptionsBuilder()
.WithKey("test")
.Build();
var reRoute = new DownstreamReRouteBuilder()
.WithQosOptions(dontUseQoSOptions)
.Build();
var reRouteTwo = new DownstreamReRouteBuilder()
.WithQosOptions(useQoSOptions)
.Build();
this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider()))
.When(x => x.WhenWeGetTheQoSProvider(reRoute))
.Then(x => x.ThenTheQoSProviderIs<FakeQoSProvider>())
.When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(reRouteTwo))
.Then(x => x.ThenTheQoSProviderIs<FakePollyQoSProvider>())
.BDDfy();
}
private void WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(DownstreamReRoute reRoute)
{
_reRoute = reRoute;
_factory.Setup(x => x.Get(_reRoute)).Returns(new FakePollyQoSProvider());
_getResult = _qosProviderHouse.Get(_reRoute);
}
private void ThenAnErrorIsReturned()
{
_getResult.IsError.ShouldBeTrue();
_getResult.Errors[0].ShouldBeOfType<UnableToFindQoSProviderError>();
}
private void ThenTheQoSProviderIs<T>()
{
_getResult.Data.ShouldBeOfType<T>();
}
private void ThenItIsAdded()
{
_getResult.IsError.ShouldBe(false);
_getResult.ShouldBeOfType<OkResponse<IQoSProvider>>();
_factory.Verify(x => x.Get(_reRoute), Times.Once);
_getResult.Data.ShouldBe(_qoSProvider);
}
private void GivenThereIsAQoSProvider(DownstreamReRoute reRoute, IQoSProvider qoSProvider)
{
_reRoute = reRoute;
_qoSProvider = qoSProvider;
_factory.Setup(x => x.Get(_reRoute)).Returns(_qoSProvider);
_getResult = _qosProviderHouse.Get(reRoute);
}
private void WhenWeGetTheQoSProvider(DownstreamReRoute reRoute)
{
_getResult = _qosProviderHouse.Get(reRoute);
}
private void ThenItIsReturned()
{
_getResult.Data.ShouldBe(_qoSProvider);
_factory.Verify(x => x.Get(_reRoute), Times.Once);
}
class FakeQoSProvider : IQoSProvider
{
T IQoSProvider.CircuitBreaker<T>()
{
throw new System.NotImplementedException();
}
}
class FakePollyQoSProvider : IQoSProvider
{
T IQoSProvider.CircuitBreaker<T>()
{
throw new System.NotImplementedException();
}
}
}
}
*/