mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 09:15:27 +08:00 
			
		
		
		
	added a new implementation that stores the ocelot config in consul kv store, had to change some major things and add cache settings as default
This commit is contained in:
		@@ -5,6 +5,7 @@ namespace Ocelot.Cache
 | 
			
		||||
    public interface IOcelotCache<T>
 | 
			
		||||
    {
 | 
			
		||||
        void Add(string key, T value, TimeSpan ttl);
 | 
			
		||||
        void AddAndDelete(string key, T value, TimeSpan ttl);
 | 
			
		||||
        T Get(string key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,18 @@ namespace Ocelot.Cache
 | 
			
		||||
            _cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddAndDelete(string key, T value, TimeSpan ttl)
 | 
			
		||||
        {
 | 
			
		||||
            var exists = _cacheManager.Get(key);
 | 
			
		||||
 | 
			
		||||
            if (exists != null)
 | 
			
		||||
            {
 | 
			
		||||
                _cacheManager.Remove(key);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public T Get(string key)
 | 
			
		||||
        {
 | 
			
		||||
            return _cacheManager.Get<T>(key);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ namespace Ocelot.Configuration.Creator
 | 
			
		||||
                    .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
 | 
			
		||||
                    .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
 | 
			
		||||
                    .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
 | 
			
		||||
                        TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan),
 | 
			
		||||
                        fileReRoute.RateLimitOptions.PeriodTimespan,
 | 
			
		||||
                        fileReRoute.RateLimitOptions.Limit))
 | 
			
		||||
                    .Build();
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration.Provider
 | 
			
		||||
{
 | 
			
		||||
    public interface IOcelotConfigurationProvider
 | 
			
		||||
    {
 | 
			
		||||
        Response<IOcelotConfiguration> Get();
 | 
			
		||||
        Task<Response<IOcelotConfiguration>> Get();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Ocelot.Configuration.Repository;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Configuration.Repository;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration.Provider
 | 
			
		||||
@@ -15,9 +16,9 @@ namespace Ocelot.Configuration.Provider
 | 
			
		||||
            _repo = repo;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Response<IOcelotConfiguration> Get()
 | 
			
		||||
        public async Task<Response<IOcelotConfiguration>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            var repoConfig = _repo.Get();
 | 
			
		||||
            var repoConfig = await _repo.Get();
 | 
			
		||||
 | 
			
		||||
            if (repoConfig.IsError)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Polly.Timeout;
 | 
			
		||||
using Polly.Timeout;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration
 | 
			
		||||
{
 | 
			
		||||
@@ -12,17 +11,17 @@ namespace Ocelot.Configuration
 | 
			
		||||
            TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
 | 
			
		||||
        {
 | 
			
		||||
            ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
 | 
			
		||||
            DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak);
 | 
			
		||||
            TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue);
 | 
			
		||||
            DurationOfBreak = durationofBreak;
 | 
			
		||||
            TimeoutValue = timeoutValue;
 | 
			
		||||
            TimeoutStrategy = timeoutStrategy;
 | 
			
		||||
        }
 | 
			
		||||
         
 | 
			
		||||
 | 
			
		||||
        public int ExceptionsAllowedBeforeBreaking { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public TimeSpan DurationOfBreak { get; private set; }
 | 
			
		||||
        public int DurationOfBreak { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public TimeSpan TimeoutValue { get; private set; }
 | 
			
		||||
        public int TimeoutValue { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public TimeoutStrategy TimeoutStrategy { get; private set; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
@@ -59,25 +58,4 @@ namespace Ocelot.Configuration
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool DisableRateLimitHeaders { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class RateLimitRule
 | 
			
		||||
    {
 | 
			
		||||
        public RateLimitRule(string period, TimeSpan periodTimespan, long limit)
 | 
			
		||||
        {
 | 
			
		||||
            Period = period;
 | 
			
		||||
            PeriodTimespan = periodTimespan;
 | 
			
		||||
            Limit = limit;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Rate limit period as in 1s, 1m, 1h,1d
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string Period { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public TimeSpan PeriodTimespan { get; private set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Maximum number of requests that a client can make in a defined period
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public long Limit { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								src/Ocelot/Configuration/RateLimitRule.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Ocelot/Configuration/RateLimitRule.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration
 | 
			
		||||
{
 | 
			
		||||
    public class RateLimitRule
 | 
			
		||||
    {
 | 
			
		||||
        public RateLimitRule(string period, double periodTimespan, long limit)
 | 
			
		||||
        {
 | 
			
		||||
            Period = period;
 | 
			
		||||
            PeriodTimespan = periodTimespan;
 | 
			
		||||
            Limit = limit;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Rate limit period as in 1s, 1m, 1h,1d
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string Period { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public double PeriodTimespan { get; private set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Maximum number of requests that a client can make in a defined period
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public long Limit { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,12 +7,12 @@ namespace Ocelot.Configuration
 | 
			
		||||
    public class ReRoute
 | 
			
		||||
    {
 | 
			
		||||
        public ReRoute(PathTemplate downstreamPathTemplate, 
 | 
			
		||||
            PathTemplate upstreamTemplate, 
 | 
			
		||||
            PathTemplate upstreamPathTemplate, 
 | 
			
		||||
            HttpMethod upstreamHttpMethod, 
 | 
			
		||||
            string upstreamTemplatePattern, 
 | 
			
		||||
            bool isAuthenticated, 
 | 
			
		||||
            AuthenticationOptions authenticationOptions, 
 | 
			
		||||
            List<ClaimToThing> configurationHeaderExtractorProperties, 
 | 
			
		||||
            List<ClaimToThing> claimsToHeaders, 
 | 
			
		||||
            List<ClaimToThing> claimsToClaims, 
 | 
			
		||||
            Dictionary<string, string> routeClaimsRequirement, 
 | 
			
		||||
            bool isAuthorised, 
 | 
			
		||||
@@ -27,8 +27,8 @@ namespace Ocelot.Configuration
 | 
			
		||||
            string reRouteKey, 
 | 
			
		||||
            ServiceProviderConfiguration serviceProviderConfiguraion,
 | 
			
		||||
            bool isQos,
 | 
			
		||||
            QoSOptions qos,
 | 
			
		||||
            bool enableRateLimit,
 | 
			
		||||
            QoSOptions qosOptions,
 | 
			
		||||
            bool enableEndpointRateLimiting,
 | 
			
		||||
            RateLimitOptions ratelimitOptions)
 | 
			
		||||
        {
 | 
			
		||||
            ReRouteKey = reRouteKey;
 | 
			
		||||
@@ -37,7 +37,7 @@ namespace Ocelot.Configuration
 | 
			
		||||
            DownstreamHost = downstreamHost;
 | 
			
		||||
            DownstreamPort = downstreamPort;
 | 
			
		||||
            DownstreamPathTemplate = downstreamPathTemplate;
 | 
			
		||||
            UpstreamPathTemplate = upstreamTemplate;
 | 
			
		||||
            UpstreamPathTemplate = upstreamPathTemplate;
 | 
			
		||||
            UpstreamHttpMethod = upstreamHttpMethod;
 | 
			
		||||
            UpstreamTemplatePattern = upstreamTemplatePattern;
 | 
			
		||||
            IsAuthenticated = isAuthenticated;
 | 
			
		||||
@@ -51,12 +51,12 @@ namespace Ocelot.Configuration
 | 
			
		||||
                ?? new List<ClaimToThing>();
 | 
			
		||||
            ClaimsToClaims = claimsToClaims 
 | 
			
		||||
                ?? new List<ClaimToThing>();
 | 
			
		||||
            ClaimsToHeaders = configurationHeaderExtractorProperties 
 | 
			
		||||
            ClaimsToHeaders = claimsToHeaders 
 | 
			
		||||
                ?? new List<ClaimToThing>();
 | 
			
		||||
                DownstreamScheme = downstreamScheme;
 | 
			
		||||
            DownstreamScheme = downstreamScheme;
 | 
			
		||||
            IsQos = isQos;
 | 
			
		||||
            QosOptions = qos;
 | 
			
		||||
            EnableEndpointRateLimiting = enableRateLimit;
 | 
			
		||||
            QosOptionsOptions = qosOptions;
 | 
			
		||||
            EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting;
 | 
			
		||||
            RateLimitOptions = ratelimitOptions;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -77,12 +77,12 @@ namespace Ocelot.Configuration
 | 
			
		||||
        public CacheOptions FileCacheOptions { get; private set; }
 | 
			
		||||
        public string DownstreamScheme {get;private set;}
 | 
			
		||||
        public bool IsQos { get; private set; }
 | 
			
		||||
        public QoSOptions QosOptions { get; private set; }
 | 
			
		||||
        public QoSOptions QosOptionsOptions { get; private set; }
 | 
			
		||||
        public string LoadBalancer {get;private set;}
 | 
			
		||||
        public string DownstreamHost { get; private set; }
 | 
			
		||||
        public int DownstreamPort { get; private set; }
 | 
			
		||||
        public ServiceProviderConfiguration ServiceProviderConfiguraion { get; private set; }
 | 
			
		||||
        public bool EnableEndpointRateLimiting { get; private set; }
 | 
			
		||||
        public bool EnableEndpointEndpointRateLimiting { get; private set; }
 | 
			
		||||
        public RateLimitOptions RateLimitOptions { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Consul;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using Ocelot.ServiceDiscovery;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration.Repository
 | 
			
		||||
{
 | 
			
		||||
    public class ConsulOcelotConfigurationRepository : IOcelotConfigurationRepository
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ConsulClient _consul;
 | 
			
		||||
        private ConsulRegistryConfiguration _configuration;
 | 
			
		||||
        private string _ocelotConfiguration = "OcelotConfiguration";
 | 
			
		||||
        private Cache.IOcelotCache<IOcelotConfiguration> _cache;
 | 
			
		||||
 | 
			
		||||
        public ConsulOcelotConfigurationRepository(ConsulRegistryConfiguration consulRegistryConfiguration, Cache.IOcelotCache<IOcelotConfiguration> cache)
 | 
			
		||||
        {
 | 
			
		||||
            var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName;
 | 
			
		||||
            var consulPort = consulRegistryConfiguration?.Port ?? 8500;
 | 
			
		||||
            _configuration = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.ServiceName);
 | 
			
		||||
            _cache = cache;
 | 
			
		||||
            _consul = new ConsulClient(config =>
 | 
			
		||||
            {
 | 
			
		||||
                config.Address = new Uri($"http://{_configuration.HostName}:{_configuration.Port}");
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response<IOcelotConfiguration>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            var config = _cache.Get(_ocelotConfiguration);
 | 
			
		||||
 | 
			
		||||
            if (config != null)
 | 
			
		||||
            {
 | 
			
		||||
                return new OkResponse<IOcelotConfiguration>(config);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var queryResult = await _consul.KV.Get(_ocelotConfiguration);
 | 
			
		||||
 | 
			
		||||
            if (queryResult.Response == null)
 | 
			
		||||
            {
 | 
			
		||||
                return new OkResponse<IOcelotConfiguration>(null);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var bytes = queryResult.Response.Value;
 | 
			
		||||
 | 
			
		||||
            var json = Encoding.UTF8.GetString(bytes);
 | 
			
		||||
 | 
			
		||||
            var consulConfig = JsonConvert.DeserializeObject<OcelotConfiguration>(json);
 | 
			
		||||
 | 
			
		||||
            return new OkResponse<IOcelotConfiguration>(consulConfig);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var json = JsonConvert.SerializeObject(ocelotConfiguration);
 | 
			
		||||
 | 
			
		||||
            var bytes = Encoding.UTF8.GetBytes(json);
 | 
			
		||||
 | 
			
		||||
            var kvPair = new KVPair(_ocelotConfiguration)
 | 
			
		||||
            {
 | 
			
		||||
                Value = bytes
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var result = await _consul.KV.Put(kvPair);
 | 
			
		||||
 | 
			
		||||
            if (result.Response)
 | 
			
		||||
            {
 | 
			
		||||
                _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3));
 | 
			
		||||
 | 
			
		||||
                return new OkResponse();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ErrorResponse(new UnableToSetConfigInConsulError("Unable to set config in consul"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration.Repository
 | 
			
		||||
{
 | 
			
		||||
    public interface IOcelotConfigurationRepository
 | 
			
		||||
    {
 | 
			
		||||
        Response<IOcelotConfiguration> Get();
 | 
			
		||||
        Response AddOrReplace(IOcelotConfiguration ocelotConfiguration);
 | 
			
		||||
        Task<Response<IOcelotConfiguration>> Get();
 | 
			
		||||
        Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration.Repository
 | 
			
		||||
{
 | 
			
		||||
@@ -11,12 +12,12 @@ namespace Ocelot.Configuration.Repository
 | 
			
		||||
 | 
			
		||||
        private IOcelotConfiguration _ocelotConfiguration;
 | 
			
		||||
 | 
			
		||||
        public Response<IOcelotConfiguration> Get()
 | 
			
		||||
        public async Task<Response<IOcelotConfiguration>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            return new OkResponse<IOcelotConfiguration>(_ocelotConfiguration);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration)
 | 
			
		||||
        public async Task<Response> AddOrReplace(IOcelotConfiguration ocelotConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            lock (LockObject)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
using Ocelot.Errors;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Configuration.Repository
 | 
			
		||||
{
 | 
			
		||||
    public class UnableToSetConfigInConsulError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public UnableToSetConfigInConsulError(string message) 
 | 
			
		||||
            : base(message, OcelotErrorCode.UnableToSetConfigInConsulError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -33,7 +33,7 @@ namespace Ocelot.Configuration.Setter
 | 
			
		||||
 | 
			
		||||
            if(!config.IsError)
 | 
			
		||||
            {
 | 
			
		||||
                _configRepo.AddOrReplace(config.Data);
 | 
			
		||||
                await _configRepo.AddOrReplace(config.Data);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ErrorResponse(config.Errors);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,24 +40,32 @@ using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.DependencyInjection
 | 
			
		||||
{
 | 
			
		||||
    public static class ServiceCollectionExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
 | 
			
		||||
        public static IServiceCollection AddOcelotStoreConfigurationInConsul(this IServiceCollection services, ConsulRegistryConfiguration consulConfig)
 | 
			
		||||
        {
 | 
			
		||||
            var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
 | 
			
		||||
            var ocelotCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache);
 | 
			
		||||
            services.TryAddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
 | 
			
		||||
            services.TryAddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotCacheManager);
 | 
			
		||||
 | 
			
		||||
            services.AddSingleton<ConsulRegistryConfiguration>(consulConfig);
 | 
			
		||||
            services.AddSingleton<IOcelotConfigurationRepository, ConsulOcelotConfigurationRepository>();
 | 
			
		||||
            return services;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot)
 | 
			
		||||
        public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, Action<ConfigurationBuilderCachePart> settings)
 | 
			
		||||
        {
 | 
			
		||||
            var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
 | 
			
		||||
            var ocelotOutputCacheManager = new OcelotCacheManagerCache<HttpResponseMessage>(cacheManagerOutputCache);
 | 
			
		||||
            services.TryAddSingleton<ICacheManager<HttpResponseMessage>>(cacheManagerOutputCache);
 | 
			
		||||
            services.TryAddSingleton<IOcelotCache<HttpResponseMessage>>(ocelotOutputCacheManager);
 | 
			
		||||
 | 
			
		||||
            var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings);
 | 
			
		||||
            var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache);
 | 
			
		||||
            services.TryAddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache);
 | 
			
		||||
            services.TryAddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager);
 | 
			
		||||
 | 
			
		||||
            services.Configure<FileConfiguration>(configurationRoot);
 | 
			
		||||
            services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
 | 
			
		||||
            services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Configuration.Provider;
 | 
			
		||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
 | 
			
		||||
using Ocelot.Errors;
 | 
			
		||||
@@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder
 | 
			
		||||
            _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
 | 
			
		||||
        public async Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod)
 | 
			
		||||
        {
 | 
			
		||||
            var configuration = _configProvider.Get();
 | 
			
		||||
            var configuration = await _configProvider.Get();
 | 
			
		||||
 | 
			
		||||
            var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method.ToLower(), upstreamHttpMethod.ToLower(), StringComparison.CurrentCultureIgnoreCase));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.DownstreamRouteFinder.Finder
 | 
			
		||||
{
 | 
			
		||||
    public interface IDownstreamRouteFinder
 | 
			
		||||
    {
 | 
			
		||||
        Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
 | 
			
		||||
        Task<Response<DownstreamRoute>> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
 | 
			
		||||
 | 
			
		||||
            _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath);
 | 
			
		||||
 | 
			
		||||
            var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
 | 
			
		||||
            var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method);
 | 
			
		||||
 | 
			
		||||
            if (downstreamRoute.IsError)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,73 +1,73 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Errors.Middleware
 | 
			
		||||
{
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Errors.Middleware
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ExceptionHandlerMiddleware 
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly IRequestScopedDataRepository _requestScopedDataRepository;
 | 
			
		||||
 | 
			
		||||
        public ExceptionHandlerMiddleware(RequestDelegate next,
 | 
			
		||||
            IOcelotLoggerFactory loggerFactory, 
 | 
			
		||||
            IRequestScopedDataRepository requestScopedDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _next = next;
 | 
			
		||||
            _requestScopedDataRepository = requestScopedDataRepository;
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogDebug("ocelot pipeline started");
 | 
			
		||||
 | 
			
		||||
                _logger.LogDebug("calling next middleware");
 | 
			
		||||
 | 
			
		||||
                await _next.Invoke(context);
 | 
			
		||||
 | 
			
		||||
                _logger.LogDebug("succesfully called middleware");
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogDebug("error calling middleware");
 | 
			
		||||
 | 
			
		||||
                var message = CreateMessage(context, e);
 | 
			
		||||
                _logger.LogError(message, e);
 | 
			
		||||
                SetInternalServerErrorOnResponse(context);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _logger.LogDebug("ocelot pipeline finished");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetInternalServerErrorOnResponse(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            context.Response.OnStarting(x =>
 | 
			
		||||
            {
 | 
			
		||||
                context.Response.StatusCode = 500;
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }, context);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string CreateMessage(HttpContext context, Exception e)
 | 
			
		||||
        {
 | 
			
		||||
            var message =
 | 
			
		||||
                $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
 | 
			
		||||
 | 
			
		||||
            if (e.InnerException != null)
 | 
			
		||||
            {
 | 
			
		||||
                message =
 | 
			
		||||
                    $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
 | 
			
		||||
            }
 | 
			
		||||
            return $"{message} RequestId: {context.TraceIdentifier}";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ExceptionHandlerMiddleware 
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly IRequestScopedDataRepository _requestScopedDataRepository;
 | 
			
		||||
 | 
			
		||||
        public ExceptionHandlerMiddleware(RequestDelegate next,
 | 
			
		||||
            IOcelotLoggerFactory loggerFactory, 
 | 
			
		||||
            IRequestScopedDataRepository requestScopedDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _next = next;
 | 
			
		||||
            _requestScopedDataRepository = requestScopedDataRepository;
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogDebug("ocelot pipeline started");
 | 
			
		||||
 | 
			
		||||
                _logger.LogDebug("calling next middleware");
 | 
			
		||||
 | 
			
		||||
                await _next.Invoke(context);
 | 
			
		||||
 | 
			
		||||
                _logger.LogDebug("succesfully called middleware");
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogDebug("error calling middleware");
 | 
			
		||||
 | 
			
		||||
                var message = CreateMessage(context, e);
 | 
			
		||||
                _logger.LogError(message, e);
 | 
			
		||||
                SetInternalServerErrorOnResponse(context);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _logger.LogDebug("ocelot pipeline finished");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SetInternalServerErrorOnResponse(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            context.Response.OnStarting(x =>
 | 
			
		||||
            {
 | 
			
		||||
                context.Response.StatusCode = 500;
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }, context);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string CreateMessage(HttpContext context, Exception e)
 | 
			
		||||
        {
 | 
			
		||||
            var message =
 | 
			
		||||
                $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
 | 
			
		||||
 | 
			
		||||
            if (e.InnerException != null)
 | 
			
		||||
            {
 | 
			
		||||
                message =
 | 
			
		||||
                    $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
 | 
			
		||||
            }
 | 
			
		||||
            return $"{message} RequestId: {context.TraceIdentifier}";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@
 | 
			
		||||
        UnableToFindServiceDiscoveryProviderError,
 | 
			
		||||
        UnableToFindLoadBalancerError,
 | 
			
		||||
        RequestTimedOutError,
 | 
			
		||||
        UnableToFindQoSProviderError
 | 
			
		||||
        UnableToFindQoSProviderError,
 | 
			
		||||
        UnableToSetConfigInConsulError
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -140,15 +140,20 @@ namespace Ocelot.Middleware
 | 
			
		||||
            var configSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
 | 
			
		||||
            
 | 
			
		||||
            var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider));
 | 
			
		||||
            
 | 
			
		||||
            var config = await configSetter.Set(fileConfig.Value);
 | 
			
		||||
            
 | 
			
		||||
            if(config == null || config.IsError)
 | 
			
		||||
 | 
			
		||||
            var ocelotConfiguration = await configProvider.Get();
 | 
			
		||||
 | 
			
		||||
            if (ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError)
 | 
			
		||||
            {
 | 
			
		||||
                throw new Exception("Unable to start Ocelot: configuration was not set up correctly.");
 | 
			
		||||
                var config = await configSetter.Set(fileConfig.Value);
 | 
			
		||||
 | 
			
		||||
                if (config == null || config.IsError)
 | 
			
		||||
                {
 | 
			
		||||
                    throw new Exception("Unable to start Ocelot: configuration was not set up correctly.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var ocelotConfiguration = configProvider.Get();
 | 
			
		||||
            ocelotConfiguration = await configProvider.Get();
 | 
			
		||||
 | 
			
		||||
            if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,153 +1,153 @@
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.RateLimit.Middleware
 | 
			
		||||
{
 | 
			
		||||
    public class ClientRateLimitMiddleware : OcelotMiddleware
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly IRateLimitCounterHandler _counterHandler;
 | 
			
		||||
        private readonly ClientRateLimitProcessor _processor;
 | 
			
		||||
 | 
			
		||||
        public ClientRateLimitMiddleware(RequestDelegate next,
 | 
			
		||||
            IOcelotLoggerFactory loggerFactory,
 | 
			
		||||
            IRequestScopedDataRepository requestScopedDataRepository,
 | 
			
		||||
            IRateLimitCounterHandler counterHandler)
 | 
			
		||||
            : base(requestScopedDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _next = next;
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<ClientRateLimitMiddleware>();
 | 
			
		||||
            _counterHandler = counterHandler;
 | 
			
		||||
            _processor = new ClientRateLimitProcessor(counterHandler);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Infrastructure.RequestData;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.RateLimit.Middleware
 | 
			
		||||
{
 | 
			
		||||
    public class ClientRateLimitMiddleware : OcelotMiddleware
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly IRateLimitCounterHandler _counterHandler;
 | 
			
		||||
        private readonly ClientRateLimitProcessor _processor;
 | 
			
		||||
 | 
			
		||||
        public ClientRateLimitMiddleware(RequestDelegate next,
 | 
			
		||||
            IOcelotLoggerFactory loggerFactory,
 | 
			
		||||
            IRequestScopedDataRepository requestScopedDataRepository,
 | 
			
		||||
            IRateLimitCounterHandler counterHandler)
 | 
			
		||||
            : base(requestScopedDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.TraceMiddlewareEntry();
 | 
			
		||||
 | 
			
		||||
            var options = DownstreamRoute.ReRoute.RateLimitOptions;
 | 
			
		||||
            // check if rate limiting is enabled
 | 
			
		||||
            if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting)
 | 
			
		||||
            {
 | 
			
		||||
            _next = next;
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<ClientRateLimitMiddleware>();
 | 
			
		||||
            _counterHandler = counterHandler;
 | 
			
		||||
            _processor = new ClientRateLimitProcessor(counterHandler);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.TraceMiddlewareEntry();
 | 
			
		||||
 | 
			
		||||
            var options = DownstreamRoute.ReRoute.RateLimitOptions;
 | 
			
		||||
            // check if rate limiting is enabled
 | 
			
		||||
            if (!DownstreamRoute.ReRoute.EnableEndpointEndpointRateLimiting)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}");
 | 
			
		||||
 | 
			
		||||
                _logger.TraceInvokeNext();
 | 
			
		||||
                    await _next.Invoke(context);
 | 
			
		||||
                _logger.TraceInvokeNextCompleted();
 | 
			
		||||
                _logger.TraceMiddlewareCompleted();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // compute identity from request
 | 
			
		||||
            var identity = SetIdentity(context, options);
 | 
			
		||||
 | 
			
		||||
            // check white list
 | 
			
		||||
            if (IsWhitelisted(identity, options))
 | 
			
		||||
            {
 | 
			
		||||
                _logger.TraceInvokeNext();
 | 
			
		||||
                    await _next.Invoke(context);
 | 
			
		||||
                _logger.TraceInvokeNextCompleted();
 | 
			
		||||
                _logger.TraceMiddlewareCompleted();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // compute identity from request
 | 
			
		||||
            var identity = SetIdentity(context, options);
 | 
			
		||||
 | 
			
		||||
            // check white list
 | 
			
		||||
            if (IsWhitelisted(identity, options))
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting");
 | 
			
		||||
 | 
			
		||||
                _logger.TraceInvokeNext();
 | 
			
		||||
                    await _next.Invoke(context);
 | 
			
		||||
                _logger.TraceInvokeNextCompleted();
 | 
			
		||||
                _logger.TraceMiddlewareCompleted();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var rule = options.RateLimitRule;
 | 
			
		||||
            if (rule.Limit > 0)
 | 
			
		||||
            {
 | 
			
		||||
                // increment counter
 | 
			
		||||
                var counter = _processor.ProcessRequest(identity, options);
 | 
			
		||||
 | 
			
		||||
                // check if limit is reached
 | 
			
		||||
                if (counter.TotalRequests > rule.Limit)
 | 
			
		||||
                {
 | 
			
		||||
                    //compute retry after value
 | 
			
		||||
                    var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule);
 | 
			
		||||
 | 
			
		||||
                    // log blocked request
 | 
			
		||||
                    LogBlockedRequest(context, identity, counter, rule);
 | 
			
		||||
 | 
			
		||||
                    // break execution
 | 
			
		||||
                    await ReturnQuotaExceededResponse(context, options, retryAfter);
 | 
			
		||||
                    _logger.TraceMiddlewareCompleted();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            //set X-Rate-Limit headers for the longest period
 | 
			
		||||
            if (!options.DisableRateLimitHeaders)
 | 
			
		||||
            {
 | 
			
		||||
                var headers = _processor.GetRateLimitHeaders( context,identity, options);
 | 
			
		||||
                context.Response.OnStarting(SetRateLimitHeaders, state: headers);
 | 
			
		||||
                    await _next.Invoke(context);
 | 
			
		||||
                _logger.TraceInvokeNextCompleted();
 | 
			
		||||
                _logger.TraceMiddlewareCompleted();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var rule = options.RateLimitRule;
 | 
			
		||||
            if (rule.Limit > 0)
 | 
			
		||||
            {
 | 
			
		||||
                // increment counter
 | 
			
		||||
                var counter = _processor.ProcessRequest(identity, options);
 | 
			
		||||
 | 
			
		||||
                // check if limit is reached
 | 
			
		||||
                if (counter.TotalRequests > rule.Limit)
 | 
			
		||||
                {
 | 
			
		||||
                    //compute retry after value
 | 
			
		||||
                    var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule);
 | 
			
		||||
 | 
			
		||||
                    // log blocked request
 | 
			
		||||
                    LogBlockedRequest(context, identity, counter, rule);
 | 
			
		||||
 | 
			
		||||
                    // break execution
 | 
			
		||||
                    await ReturnQuotaExceededResponse(context, options, retryAfter);
 | 
			
		||||
                    _logger.TraceMiddlewareCompleted();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            //set X-Rate-Limit headers for the longest period
 | 
			
		||||
            if (!options.DisableRateLimitHeaders)
 | 
			
		||||
            {
 | 
			
		||||
                var headers = _processor.GetRateLimitHeaders( context,identity, options);
 | 
			
		||||
                context.Response.OnStarting(SetRateLimitHeaders, state: headers);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _logger.TraceInvokeNext();
 | 
			
		||||
                await _next.Invoke(context);
 | 
			
		||||
            _logger.TraceInvokeNextCompleted();
 | 
			
		||||
            _logger.TraceMiddlewareCompleted();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)
 | 
			
		||||
        {
 | 
			
		||||
            var clientId = "client";
 | 
			
		||||
            if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader))
 | 
			
		||||
            {
 | 
			
		||||
                clientId = httpContext.Request.Headers[option.ClientIdHeader].First();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ClientRequestIdentity(
 | 
			
		||||
                clientId,
 | 
			
		||||
                httpContext.Request.Path.ToString().ToLowerInvariant(), 
 | 
			
		||||
                httpContext.Request.Method.ToLowerInvariant()
 | 
			
		||||
                );
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
        public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option)
 | 
			
		||||
        {
 | 
			
		||||
            if (option.ClientWhitelist.Contains(requestIdentity.ClientId))
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter)
 | 
			
		||||
        {
 | 
			
		||||
            var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage;
 | 
			
		||||
 | 
			
		||||
            if (!option.DisableRateLimitHeaders)
 | 
			
		||||
            {
 | 
			
		||||
                httpContext.Response.Headers["Retry-After"] = retryAfter;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            httpContext.Response.StatusCode = option.HttpStatusCode;
 | 
			
		||||
            return httpContext.Response.WriteAsync(message);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task SetRateLimitHeaders(object rateLimitHeaders)
 | 
			
		||||
        {
 | 
			
		||||
            var headers = (RateLimitHeaders)rateLimitHeaders;
 | 
			
		||||
 | 
			
		||||
            headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit;
 | 
			
		||||
            headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining;
 | 
			
		||||
            headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset;
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            _logger.TraceMiddlewareCompleted();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)
 | 
			
		||||
        {
 | 
			
		||||
            var clientId = "client";
 | 
			
		||||
            if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader))
 | 
			
		||||
            {
 | 
			
		||||
                clientId = httpContext.Request.Headers[option.ClientIdHeader].First();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ClientRequestIdentity(
 | 
			
		||||
                clientId,
 | 
			
		||||
                httpContext.Request.Path.ToString().ToLowerInvariant(), 
 | 
			
		||||
                httpContext.Request.Method.ToLowerInvariant()
 | 
			
		||||
                );
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
        public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option)
 | 
			
		||||
        {
 | 
			
		||||
            if (option.ClientWhitelist.Contains(requestIdentity.ClientId))
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter)
 | 
			
		||||
        {
 | 
			
		||||
            var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage;
 | 
			
		||||
 | 
			
		||||
            if (!option.DisableRateLimitHeaders)
 | 
			
		||||
            {
 | 
			
		||||
                httpContext.Response.Headers["Retry-After"] = retryAfter;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            httpContext.Response.StatusCode = option.HttpStatusCode;
 | 
			
		||||
            return httpContext.Response.WriteAsync(message);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task SetRateLimitHeaders(object rateLimitHeaders)
 | 
			
		||||
        {
 | 
			
		||||
            var headers = (RateLimitHeaders)rateLimitHeaders;
 | 
			
		||||
 | 
			
		||||
            headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit;
 | 
			
		||||
            headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining;
 | 
			
		||||
            headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset;
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ namespace Ocelot.RateLimit
 | 
			
		||||
                if (entry.HasValue)
 | 
			
		||||
                {
 | 
			
		||||
                    // entry has not expired
 | 
			
		||||
                    if (entry.Value.Timestamp + rule.PeriodTimespan >= DateTime.UtcNow)
 | 
			
		||||
                    if (entry.Value.Timestamp + TimeSpan.FromSeconds(rule.PeriodTimespan) >= DateTime.UtcNow)
 | 
			
		||||
                    {
 | 
			
		||||
                        // increment request count
 | 
			
		||||
                        var totalRequests = entry.Value.TotalRequests + 1;
 | 
			
		||||
@@ -45,7 +45,7 @@ namespace Ocelot.RateLimit
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // stores: id (string) - timestamp (datetime) - total_requests (long)
 | 
			
		||||
                _counterHandler.Set(counterId, counter, rule.PeriodTimespan);
 | 
			
		||||
                _counterHandler.Set(counterId, counter, TimeSpan.FromSeconds(rule.PeriodTimespan));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return counter;
 | 
			
		||||
@@ -95,7 +95,7 @@ namespace Ocelot.RateLimit
 | 
			
		||||
        public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
 | 
			
		||||
        {
 | 
			
		||||
            var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
 | 
			
		||||
            var retryAfter = Convert.ToInt32(rule.PeriodTimespan.TotalSeconds);
 | 
			
		||||
            var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
 | 
			
		||||
            retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
 | 
			
		||||
            return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -57,15 +57,15 @@ namespace Ocelot.Requester.QoS
 | 
			
		||||
        {
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<PollyQoSProvider>();
 | 
			
		||||
 | 
			
		||||
            _timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy);
 | 
			
		||||
            _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.TimeoutValue), reRoute.QosOptionsOptions.TimeoutStrategy);
 | 
			
		||||
 | 
			
		||||
            _circuitBreakerPolicy = Policy
 | 
			
		||||
                .Handle<HttpRequestException>()
 | 
			
		||||
                .Or<TimeoutRejectedException>()
 | 
			
		||||
                .Or<TimeoutException>()
 | 
			
		||||
                .CircuitBreakerAsync(
 | 
			
		||||
                    exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
 | 
			
		||||
                    durationOfBreak: reRoute.QosOptions.DurationOfBreak,
 | 
			
		||||
                    exceptionsAllowedBeforeBreaking: reRoute.QosOptionsOptions.ExceptionsAllowedBeforeBreaking,
 | 
			
		||||
                    durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptionsOptions.DurationOfBreak),
 | 
			
		||||
                    onBreak: (ex, breakDelay) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.LogError(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user