Refactor HttpClientHttpRequester Cache HttpClient

This commit is contained in:
geffzhang 2017-03-04 18:18:00 +08:00
parent 1f2d77e8f9
commit 10db534008
8 changed files with 243 additions and 53 deletions

View File

@ -135,6 +135,7 @@ namespace Ocelot.DependencyInjection
services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
services.AddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
services.AddSingleton<IHttpClientMessageCacheHandler, MemoryHttpClientMessageCacheHandler>();
// 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,46 +1,110 @@
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 TimeSpan? _timeout;
private readonly List<DelegatingHandler> _handlers = new List<DelegatingHandler>();
private Dictionary<string, string> _defaultHeaders;
private CookieContainer _cookieContainer;
private IQoSProvider _qoSProvider;
public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler)
public IHttpClientBuilder WithCookieContainer(CookieContainer cookieContainer)
{
_handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler));
_cookieContainer = cookieContainer;
return this;
}
internal HttpClient Build(HttpMessageHandler innerHandler)
public IHttpClientBuilder WithTimeout(TimeSpan timeout)
{
return _handlers.Any() ?
new HttpClient(CreateHttpMessageHandler()) :
new HttpClient(innerHandler);
_timeout = timeout;
return this;
}
private HttpMessageHandler CreateHttpMessageHandler()
public IHttpClientBuilder WithHandler(DelegatingHandler handler)
{
HttpMessageHandler httpMessageHandler = new HttpClientHandler();
_handlers.Add(handler);
return this;
}
_handlers
.OrderByDescending(handler => handler.Key)
.Select(handler => handler.Value)
.Reverse()
.ToList()
.ForEach(handler =>
public IHttpClientBuilder WithDefaultRequestHeaders(Dictionary<string, string> headers)
{
var delegatingHandler = handler();
delegatingHandler.InnerHandler = httpMessageHandler;
httpMessageHandler = delegatingHandler;
});
_defaultHeaders = headers;
return this;
}
public IHttpClient Create()
{
HttpClientHandler httpclientHandler = null;
if (_cookieContainer != null)
{
httpclientHandler = new HttpClientHandler() { CookieContainer = _cookieContainer };
}
else
{
httpclientHandler = new HttpClientHandler();
}
if (httpclientHandler.SupportsAutomaticDecompression)
{
httpclientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler));
if (_timeout.HasValue)
{
client.Timeout = _timeout.Value;
}
if (_defaultHeaders == null)
{
return new HttpClientWrapper(client);
}
foreach (var header in _defaultHeaders)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
return new HttpClientWrapper(client);
}
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler)
{
foreach (var handler in _handlers)
{
handler.InnerHandler = httpMessageHandler;
httpMessageHandler = handler;
}
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,26 +11,36 @@ namespace Ocelot.Requester
{
public class HttpClientHttpRequester : IHttpRequester
{
private IHttpClientMessageCacheHandler _cacheHandlers;
private readonly IOcelotLogger _logger;
public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory)
public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientMessageCacheHandler cacheHandlers)
{
_logger = loggerFactory.CreateLogger<HttpClientHttpRequester>();
_cacheHandlers = cacheHandlers;
}
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
{
var builder = new HttpClientBuilder();
using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer })
{
builder.WithCookieContainer(request.CookieContainer);
string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}";
if (request.IsQos)
{
builder.WithQoS(request.QosProvider, _logger, handler);
builder.WithHandler(new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _logger));
baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
}
using (var httpClient = builder.Build(handler))
IHttpClient httpClient = _cacheHandlers.Get(baseUrl);
if (httpClient == null)
{
httpClient = builder.Create();
_cacheHandlers.Set(baseUrl, httpClient, TimeSpan.FromMinutes(30));
}
try
{
var response = await httpClient.SendAsync(request.HttpRequestMessage);
@ -49,8 +60,7 @@ namespace Ocelot.Requester
{
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
}
}
}
}
}
}

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,42 @@
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 the cookie container used to store server cookies by the handler
/// </summary>
/// <param name="cookieContainer"></param>
/// <returns></returns>
IHttpClientBuilder WithCookieContainer(CookieContainer cookieContainer);
/// <summary>
/// Sets the number of milliseconds to wait before the request times out.
/// </summary>
IHttpClientBuilder WithTimeout(TimeSpan timeout);
/// <summary>
/// Sets a DelegatingHandler.
/// Can be reused to set several different Handlers in a pipeline.
/// </summary>
IHttpClientBuilder WithHandler(DelegatingHandler handler);
/// <summary>
/// Sets Default HttpRequestHeaders
/// </summary>
IHttpClientBuilder WithDefaultRequestHeaders(Dictionary<string, string> headers);
/// <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 IHttpClientMessageCacheHandler
{
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 MemoryHttpClientMessageCacheHandler : IHttpClientMessageCacheHandler
{
private readonly IMemoryCache _memoryCache;
public MemoryHttpClientMessageCacheHandler(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void Set(string id, IHttpClient counter, TimeSpan expirationTime)
{
_memoryCache.Set(id, counter, 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);
}
}
}

View File

@ -16,9 +16,7 @@ namespace Ocelot.Requester
public PollyCircuitBreakingDelegatingHandler(
IQoSProvider qoSProvider,
IOcelotLogger logger,
HttpMessageHandler innerHandler)
: base(innerHandler)
IOcelotLogger logger)
{
_qoSProvider = qoSProvider;
_logger = logger;