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<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
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
// could maybe use a scoped data repository

View File

@ -1,32 +1,48 @@
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<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;
}
internal HttpClient Build()
public IHttpClient Create()
{
return _handlers.Any() ?
new HttpClient(CreateHttpMessageHandler()) :
new HttpClient();
var httpclientHandler = new HttpClientHandler();
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
.OrderByDescending(handler => handler.Key)
@ -39,8 +55,25 @@ namespace Ocelot.Requester
delegatingHandler.InnerHandler = httpMessageHandler;
httpMessageHandler = delegatingHandler;
});
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.Collections.Concurrent;
using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging;
@ -10,24 +11,28 @@ 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<HttpClientHttpRequester>();
_cacheHandlers = cacheHandlers;
}
public async Task<Response<HttpResponseMessage>> 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
{
var response = await httpClient.SendAsync(request.HttpRequestMessage);
@ -47,7 +52,20 @@ namespace Ocelot.Requester
{
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);
}
}
}