mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 20:10:50 +08:00 
			
		
		
		
	Refactor HttpClientHttpRequester Cache HttpClient
This commit is contained in:
		@@ -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)
 | 
			
		||||
        {
 | 
			
		||||
            _defaultHeaders = headers;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public IHttpClient Create()
 | 
			
		||||
        {
 | 
			
		||||
            HttpClientHandler httpclientHandler = null;
 | 
			
		||||
            if (_cookieContainer != null)
 | 
			
		||||
            {
 | 
			
		||||
                var delegatingHandler = handler();
 | 
			
		||||
                delegatingHandler.InnerHandler = httpMessageHandler;
 | 
			
		||||
                httpMessageHandler = delegatingHandler;
 | 
			
		||||
            });
 | 
			
		||||
                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,47 +11,56 @@ 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();    
 | 
			
		||||
            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)
 | 
			
		||||
            {
 | 
			
		||||
                if (request.IsQos)
 | 
			
		||||
                {
 | 
			
		||||
                    builder.WithQoS(request.QosProvider, _logger, handler);
 | 
			
		||||
                }           
 | 
			
		||||
 | 
			
		||||
                using (var httpClient = builder.Build(handler))
 | 
			
		||||
                {
 | 
			
		||||
                    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));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                builder.WithHandler(new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _logger));
 | 
			
		||||
                baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user