Merge pull request #57 from geffzhang/feature/Optimization-HttpClient-instance

Feature/optimization http client instance
This commit is contained in:
Tom Pallister 2017-03-08 12:52:24 +00:00 committed by GitHub
commit 9bb86122f8
7 changed files with 195 additions and 43 deletions

View File

@ -143,6 +143,7 @@ namespace Ocelot.DependencyInjection
services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>(); services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>(); services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
services.AddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>(); services.AddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
services.AddSingleton<IHttpClientCache, MemoryHttpClientCache>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // 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 // could maybe use a scoped data repository

View File

@ -1,32 +1,48 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
internal class HttpClientBuilder internal class HttpClientBuilder : IHttpClientBuilder
{ {
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>(); private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
private Dictionary<string, string> _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; return this;
} }
internal HttpClient Build() public IHttpClient Create()
{ {
return _handlers.Any() ? var httpclientHandler = new HttpClientHandler();
new HttpClient(CreateHttpMessageHandler()) :
new HttpClient(); var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler));
if (_defaultHeaders == null)
{
return new HttpClientWrapper(client);
} }
private HttpMessageHandler CreateHttpMessageHandler() foreach (var header in _defaultHeaders)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
return new HttpClientWrapper(client);
}
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler)
{ {
HttpMessageHandler httpMessageHandler = new HttpClientHandler();
_handlers _handlers
.OrderByDescending(handler => handler.Key) .OrderByDescending(handler => handler.Key)
@ -39,8 +55,25 @@ namespace Ocelot.Requester
delegatingHandler.InnerHandler = httpMessageHandler; delegatingHandler.InnerHandler = httpMessageHandler;
httpMessageHandler = delegatingHandler; httpMessageHandler = delegatingHandler;
}); });
return httpMessageHandler; return httpMessageHandler;
} }
} }
/// <summary>
/// This class was made to make unit testing easier when HttpClient is used.
/// </summary>
internal class HttpClientWrapper : IHttpClient
{
public HttpClient Client { get; }
public HttpClientWrapper(HttpClient client)
{
Client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return Client.SendAsync(request);
}
}
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Logging; using Ocelot.Logging;
@ -10,24 +11,28 @@ namespace Ocelot.Requester
{ {
public class HttpClientHttpRequester : IHttpRequester public class HttpClientHttpRequester : IHttpRequester
{ {
private readonly IHttpClientCache _cacheHandlers;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers)
{ {
_logger = loggerFactory.CreateLogger<HttpClientHttpRequester>(); _logger = loggerFactory.CreateLogger<HttpClientHttpRequester>();
_cacheHandlers = cacheHandlers;
} }
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
{ {
var builder = new HttpClientBuilder(); 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); var response = await httpClient.SendAsync(request.HttpRequestMessage);
@ -47,7 +52,20 @@ namespace Ocelot.Requester
{ {
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception)); return new ErrorResponse<HttpResponseMessage>(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;
} }
} }
} }

View File

@ -0,0 +1,13 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Ocelot.Requester
{
public interface IHttpClient
{
HttpClient Client { get; }
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
}
}

View File

@ -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
{
/// <summary>
/// Sets a PollyCircuitBreakingDelegatingHandler .
/// </summary>
IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger);
/// <summary>
/// Creates the <see cref="HttpClient"/>
/// </summary>
IHttpClient Create();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}