mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-23 00:32:50 +08:00
Refactor HttpClientHttpRequester Cache HttpClient
This commit is contained in:
parent
1f2d77e8f9
commit
10db534008
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
13
src/Ocelot/Requester/IHttpClient.cs
Normal file
13
src/Ocelot/Requester/IHttpClient.cs
Normal 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);
|
||||
}
|
||||
}
|
42
src/Ocelot/Requester/IHttpClientBuilder.cs
Normal file
42
src/Ocelot/Requester/IHttpClientBuilder.cs
Normal 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();
|
||||
}
|
||||
}
|
16
src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs
Normal file
16
src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
46
src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs
Normal file
46
src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,7 @@ namespace Ocelot.Requester
|
||||
|
||||
public PollyCircuitBreakingDelegatingHandler(
|
||||
IQoSProvider qoSProvider,
|
||||
IOcelotLogger logger,
|
||||
HttpMessageHandler innerHandler)
|
||||
: base(innerHandler)
|
||||
IOcelotLogger logger)
|
||||
{
|
||||
_qoSProvider = qoSProvider;
|
||||
_logger = logger;
|
||||
|
Loading…
x
Reference in New Issue
Block a user