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<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>(); services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
services.AddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>(); 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 // 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,46 +1,110 @@
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 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; return this;
} }
internal HttpClient Build(HttpMessageHandler innerHandler) public IHttpClientBuilder WithTimeout(TimeSpan timeout)
{ {
return _handlers.Any() ? _timeout = timeout;
new HttpClient(CreateHttpMessageHandler()) : return this;
new HttpClient(innerHandler);
} }
private HttpMessageHandler CreateHttpMessageHandler() public IHttpClientBuilder WithHandler(DelegatingHandler handler)
{ {
HttpMessageHandler httpMessageHandler = new HttpClientHandler(); _handlers.Add(handler);
return this;
}
_handlers public IHttpClientBuilder WithDefaultRequestHeaders(Dictionary<string, string> headers)
.OrderByDescending(handler => handler.Key) {
.Select(handler => handler.Value) _defaultHeaders = headers;
.Reverse() return this;
.ToList() }
.ForEach(handler =>
public IHttpClient Create()
{
HttpClientHandler httpclientHandler = null;
if (_cookieContainer != null)
{ {
var delegatingHandler = handler(); httpclientHandler = new HttpClientHandler() { CookieContainer = _cookieContainer };
delegatingHandler.InnerHandler = httpMessageHandler; }
httpMessageHandler = delegatingHandler; 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; 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,47 +11,56 @@ namespace Ocelot.Requester
{ {
public class HttpClientHttpRequester : IHttpRequester public class HttpClientHttpRequester : IHttpRequester
{ {
private IHttpClientMessageCacheHandler _cacheHandlers;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientMessageCacheHandler 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();
using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) builder.WithCookieContainer(request.CookieContainer);
{
if (request.IsQos)
{
builder.WithQoS(request.QosProvider, _logger, handler);
}
using (var httpClient = builder.Build(handler)) string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}";
{
try if (request.IsQos)
{ {
var response = await httpClient.SendAsync(request.HttpRequestMessage); builder.WithHandler(new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _logger));
return new OkResponse<HttpResponseMessage>(response); baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
}
catch (TimeoutRejectedException exception)
{
return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (BrokenCircuitException exception)
{
return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (Exception exception)
{
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
}
}
} }
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);
return new OkResponse<HttpResponseMessage>(response);
}
catch (TimeoutRejectedException exception)
{
return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (BrokenCircuitException exception)
{
return
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
}
catch (Exception exception)
{
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( public PollyCircuitBreakingDelegatingHandler(
IQoSProvider qoSProvider, IQoSProvider qoSProvider,
IOcelotLogger logger, IOcelotLogger logger)
HttpMessageHandler innerHandler)
: base(innerHandler)
{ {
_qoSProvider = qoSProvider; _qoSProvider = qoSProvider;
_logger = logger; _logger = logger;