mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 14:02:49 +08:00
Merge pull request #85 from TomPallister/feature/config-in-consul
added a new implementation that stores the ocelot config in consul kv…
This commit is contained in:
commit
8d31b40c21
@ -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)
|
||||
{
|
||||
|
@ -27,6 +27,7 @@
|
||||
UnableToFindServiceDiscoveryProviderError,
|
||||
UnableToFindLoadBalancerError,
|
||||
RequestTimedOutError,
|
||||
UnableToFindQoSProviderError
|
||||
UnableToFindQoSProviderError,
|
||||
UnableToSetConfigInConsulError
|
||||
}
|
||||
}
|
||||
|
@ -141,14 +141,19 @@ namespace Ocelot.Middleware
|
||||
|
||||
var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider));
|
||||
|
||||
var config = await configSetter.Set(fileConfig.Value);
|
||||
var ocelotConfiguration = await configProvider.Get();
|
||||
|
||||
if(config == null || config.IsError)
|
||||
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)
|
||||
{
|
||||
|
@ -35,7 +35,7 @@ namespace Ocelot.RateLimit.Middleware
|
||||
|
||||
var options = DownstreamRoute.ReRoute.RateLimitOptions;
|
||||
// check if rate limiting is enabled
|
||||
if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting)
|
||||
if (!DownstreamRoute.ReRoute.EnableEndpointEndpointRateLimiting)
|
||||
{
|
||||
_logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}");
|
||||
|
||||
|
@ -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(
|
||||
|
171
test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs
Normal file
171
test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Consul;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.AcceptanceTests
|
||||
{
|
||||
public class ConfigurationInConsul : IDisposable
|
||||
{
|
||||
private IWebHost _builder;
|
||||
private readonly Steps _steps;
|
||||
private IWebHost _fakeConsulBuilder;
|
||||
private IOcelotConfiguration _config;
|
||||
|
||||
public ConfigurationInConsul()
|
||||
{
|
||||
_steps = new Steps();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void should_return_response_200_with_simple_url()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHost = "localhost",
|
||||
DownstreamPort = 51779,
|
||||
UpstreamPathTemplate = "/",
|
||||
UpstreamHttpMethod = "Get",
|
||||
|
||||
}
|
||||
},
|
||||
GlobalConfiguration = new FileGlobalConfiguration()
|
||||
{
|
||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||
{
|
||||
Provider = "Consul",
|
||||
Host = "localhost",
|
||||
Port = 9500
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
|
||||
|
||||
var consulConfig = new ConsulRegistryConfiguration("localhost", 9500, "Ocelot");
|
||||
|
||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", 200, "Hello from Laura"))
|
||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig(consulConfig))
|
||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
|
||||
{
|
||||
_fakeConsulBuilder = new WebHostBuilder()
|
||||
.UseUrls(url)
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseUrls(url)
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(async context =>
|
||||
{
|
||||
if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration")
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(_config);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
|
||||
var kvp = new FakeConsulGetResponse(base64);
|
||||
|
||||
await context.Response.WriteJsonAsync(new FakeConsulGetResponse[]{kvp});
|
||||
}
|
||||
|
||||
else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration")
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new StreamReader(context.Request.Body);
|
||||
|
||||
var json = reader.ReadToEnd();
|
||||
|
||||
_config = JsonConvert.DeserializeObject<OcelotConfiguration>(json);
|
||||
|
||||
var response = JsonConvert.SerializeObject(true);
|
||||
|
||||
await context.Response.WriteAsync(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
_fakeConsulBuilder.Start();
|
||||
}
|
||||
|
||||
public class FakeConsulGetResponse
|
||||
{
|
||||
public FakeConsulGetResponse(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public int CreateIndex => 100;
|
||||
public int ModifyIndex => 200;
|
||||
public int LockIndex => 200;
|
||||
public string Key => "OcelotConfiguration";
|
||||
public int Flags => 0;
|
||||
public string Value { get; private set; }
|
||||
public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e";
|
||||
}
|
||||
|
||||
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
|
||||
{
|
||||
_builder = new WebHostBuilder()
|
||||
.UseUrls(url)
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseUrls(url)
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(async context =>
|
||||
{
|
||||
context.Response.StatusCode = statusCode;
|
||||
await context.Response.WriteAsync(responseBody);
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
_builder.Start();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_builder?.Dispose();
|
||||
_steps.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -15,9 +15,11 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.DependencyInjection;
|
||||
using Ocelot.ManualTest;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Shouldly;
|
||||
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
|
||||
|
||||
@ -84,6 +86,22 @@ namespace Ocelot.AcceptanceTests
|
||||
_ocelotClient = _ocelotServer.CreateClient();
|
||||
}
|
||||
|
||||
public void GivenOcelotIsRunningUsingConsulToStoreConfig(ConsulRegistryConfiguration consulConfig)
|
||||
{
|
||||
_webHostBuilder = new WebHostBuilder();
|
||||
|
||||
_webHostBuilder.ConfigureServices(s =>
|
||||
{
|
||||
s.AddSingleton(_webHostBuilder);
|
||||
s.AddOcelotStoreConfigurationInConsul(consulConfig);
|
||||
});
|
||||
|
||||
_ocelotServer = new TestServer(_webHostBuilder
|
||||
.UseStartup<Startup>());
|
||||
|
||||
_ocelotClient = _ocelotServer.CreateClient();
|
||||
}
|
||||
|
||||
internal void ThenTheResponseShouldBe(FileConfiguration expected)
|
||||
{
|
||||
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
|
||||
@ -138,8 +156,7 @@ namespace Ocelot.AcceptanceTests
|
||||
.WithDictionaryHandle();
|
||||
};
|
||||
|
||||
s.AddOcelotOutputCaching(settings);
|
||||
s.AddOcelot(configuration);
|
||||
s.AddOcelot(configuration, settings);
|
||||
})
|
||||
.ConfigureLogging(l =>
|
||||
{
|
||||
|
@ -37,8 +37,8 @@ namespace Ocelot.ManualTest
|
||||
})
|
||||
.WithDictionaryHandle();
|
||||
};
|
||||
services.AddOcelotOutputCaching(settings);
|
||||
services.AddOcelot(Configuration);
|
||||
|
||||
services.AddOcelot(Configuration, settings);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
|
@ -690,10 +690,10 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
private void ThenTheQosOptionsAre(QoSOptions qosOptions)
|
||||
{
|
||||
_config.Data.ReRoutes[0].QosOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak);
|
||||
_config.Data.ReRoutes[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak);
|
||||
|
||||
_config.Data.ReRoutes[0].QosOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking);
|
||||
_config.Data.ReRoutes[0].QosOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue);
|
||||
_config.Data.ReRoutes[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking);
|
||||
_config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
private void WhenIGetTheConfiguration()
|
||||
{
|
||||
_getResult = _repo.Get();
|
||||
_getResult = _repo.Get().Result;
|
||||
}
|
||||
|
||||
private void GivenThereIsASavedConfiguration()
|
||||
@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
private void WhenIAddOrReplaceTheConfig()
|
||||
{
|
||||
_result = _repo.AddOrReplace(_config);
|
||||
_result = _repo.AddOrReplace(_config).Result;
|
||||
}
|
||||
|
||||
private void ThenNoErrorsAreReturned()
|
||||
|
@ -53,12 +53,12 @@ namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
_configurationRepository
|
||||
.Setup(x => x.Get())
|
||||
.Returns(config);
|
||||
.ReturnsAsync(config);
|
||||
}
|
||||
|
||||
private void WhenIGetTheConfig()
|
||||
{
|
||||
_result = _ocelotConfigurationProvider.Get();
|
||||
_result = _ocelotConfigurationProvider.Get().Result;
|
||||
}
|
||||
|
||||
private void TheFollowingIsReturned(Response<IOcelotConfiguration> expected)
|
||||
|
@ -59,7 +59,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.WithQuotaExceededMessage("QuotaExceededMessage")
|
||||
.WithRateLimitCounterPrefix("RateLimitCounterPrefix")
|
||||
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
|
||||
TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan),
|
||||
fileReRoute.RateLimitOptions.PeriodTimespan,
|
||||
fileReRoute.RateLimitOptions.Limit))
|
||||
.Build();
|
||||
|
||||
@ -102,7 +102,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
_result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix);
|
||||
_result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit);
|
||||
_result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period);
|
||||
_result.RateLimitRule.PeriodTimespan.Ticks.ShouldBe(expected.RateLimitRule.PeriodTimespan.Ticks);
|
||||
TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
_downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
|
||||
_downstreamRouteFinder
|
||||
.Setup(x => x.FindDownstreamRoute(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(_downstreamRoute);
|
||||
.ReturnsAsync(_downstreamRoute);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
_reRoutesConfig = reRoutesConfig;
|
||||
_mockConfig
|
||||
.Setup(x => x.Get())
|
||||
.Returns(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig, adminPath)));
|
||||
.ReturnsAsync(new OkResponse<IOcelotConfiguration>(new OcelotConfiguration(_reRoutesConfig, adminPath)));
|
||||
}
|
||||
|
||||
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
|
||||
@ -209,7 +209,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
|
||||
private void WhenICallTheFinder()
|
||||
{
|
||||
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod);
|
||||
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result;
|
||||
}
|
||||
|
||||
private void ThenTheFollowingIsReturned(DownstreamRoute expected)
|
||||
|
@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.RateLimit
|
||||
{
|
||||
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
|
||||
new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions(
|
||||
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429))
|
||||
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429))
|
||||
.WithUpstreamHttpMethod("Get")
|
||||
.Build());
|
||||
|
||||
@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.RateLimit
|
||||
{
|
||||
var downstreamRoute = new DownstreamRoute(new List<Ocelot.DownstreamRouteFinder.UrlMatcher.UrlPathPlaceholderNameAndValue>(),
|
||||
new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions(
|
||||
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429))
|
||||
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429))
|
||||
.WithUpstreamHttpMethod("Get")
|
||||
.Build());
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user