diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index e99b2490..508f8ad7 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -143,6 +143,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 71462644..3af3b9be 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,46 +1,79 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; +using System.Threading.Tasks; using Ocelot.Logging; using Ocelot.Requester.QoS; namespace Ocelot.Requester { - internal class HttpClientBuilder + internal class HttpClientBuilder : IHttpClientBuilder { private readonly Dictionary> _handlers = new Dictionary>(); + private Dictionary _defaultHeaders; - public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger) + + public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) { - _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger)); + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); + return this; + } + + public IHttpClient Create() + { + var httpclientHandler = new HttpClientHandler(); + + var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); + + if (_defaultHeaders == null) + { + return new HttpClientWrapper(client); + } + + foreach (var header in _defaultHeaders) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + + return new HttpClientWrapper(client); } - internal HttpClient Build() - { - return _handlers.Any() ? - new HttpClient(CreateHttpMessageHandler()) : - new HttpClient(); - } - - private HttpMessageHandler CreateHttpMessageHandler() - { - HttpMessageHandler httpMessageHandler = new HttpClientHandler(); - + private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) + { + _handlers .OrderByDescending(handler => handler.Key) .Select(handler => handler.Value) .Reverse() .ToList() .ForEach(handler => - { - var delegatingHandler = handler(); - delegatingHandler.InnerHandler = httpMessageHandler; - httpMessageHandler = delegatingHandler; - }); - + { + var delegatingHandler = handler(); + delegatingHandler.InnerHandler = httpMessageHandler; + httpMessageHandler = delegatingHandler; + }); return httpMessageHandler; } } + + /// + /// This class was made to make unit testing easier when HttpClient is used. + /// + internal class HttpClientWrapper : IHttpClient + { + public HttpClient Client { get; } + + public HttpClientWrapper(HttpClient client) + { + Client = client; + } + + public Task SendAsync(HttpRequestMessage request) + { + return Client.SendAsync(request); + } + } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 08839ccc..0cf6a053 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Net.Http; using System.Threading.Tasks; using Ocelot.Logging; @@ -10,44 +11,61 @@ namespace Ocelot.Requester { public class HttpClientHttpRequester : IHttpRequester { + private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers) { _logger = loggerFactory.CreateLogger(); + _cacheHandlers = cacheHandlers; } public async Task> GetResponse(Request.Request request) { var builder = new HttpClientBuilder(); - if (request.IsQos) + var cacheKey = GetCacheKey(request, builder); + + var httpClient = _cacheHandlers.Get(cacheKey); + if (httpClient == null) { - builder.WithQoS(request.QosProvider, _logger); + httpClient = builder.Create(); + _cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(6)); } - using (var httpClient = builder.Build()) + try { - try - { - var response = await httpClient.SendAsync(request.HttpRequestMessage); - return new OkResponse(response); - } - catch (TimeoutRejectedException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (BrokenCircuitException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (Exception exception) - { - return new ErrorResponse(new UnableToCompleteRequestError(exception)); - } + var response = await httpClient.SendAsync(request.HttpRequestMessage); + return new OkResponse(response); } + catch (TimeoutRejectedException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (BrokenCircuitException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (Exception exception) + { + return new ErrorResponse(new UnableToCompleteRequestError(exception)); + } + + } + + private string GetCacheKey(Request.Request request, IHttpClientBuilder builder) + { + string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; + + if (request.IsQos) + { + builder.WithQos(request.QosProvider, _logger); + baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}"; + } + + return baseUrl; } } } \ No newline at end of file diff --git a/src/Ocelot/Requester/IHttpClient.cs b/src/Ocelot/Requester/IHttpClient.cs new file mode 100644 index 00000000..8a169a36 --- /dev/null +++ b/src/Ocelot/Requester/IHttpClient.cs @@ -0,0 +1,13 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public interface IHttpClient + { + HttpClient Client { get; } + + Task SendAsync(HttpRequestMessage request); + } +} diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs new file mode 100644 index 00000000..832fd8d1 --- /dev/null +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using System.Net; + +namespace Ocelot.Requester +{ + public interface IHttpClientBuilder + { + + /// + /// Sets a PollyCircuitBreakingDelegatingHandler . + /// + IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger); + + /// + /// Creates the + /// + IHttpClient Create(); + } +} diff --git a/src/Ocelot/Requester/IHttpClientCache.cs b/src/Ocelot/Requester/IHttpClientCache.cs new file mode 100644 index 00000000..f4b8f76e --- /dev/null +++ b/src/Ocelot/Requester/IHttpClientCache.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public interface IHttpClientCache + { + bool Exists(string id); + IHttpClient Get(string id); + void Remove(string id); + void Set(string id, IHttpClient handler, TimeSpan expirationTime); + } +} diff --git a/src/Ocelot/Requester/MemoryHttpClientCache.cs b/src/Ocelot/Requester/MemoryHttpClientCache.cs new file mode 100644 index 00000000..33ff15a1 --- /dev/null +++ b/src/Ocelot/Requester/MemoryHttpClientCache.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public class MemoryHttpClientCache : IHttpClientCache + { + private readonly IMemoryCache _memoryCache; + + public MemoryHttpClientCache(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, IHttpClient client, TimeSpan expirationTime) + { + _memoryCache.Set(id, client, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + IHttpClient counter; + return _memoryCache.TryGetValue(id, out counter); + } + + public IHttpClient Get(string id) + { + IHttpClient counter; + if (_memoryCache.TryGetValue(id, out counter)) + { + return counter; + } + + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +}