From e1d5ef3aaefe567af4100de35afacbdde32936bc Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sat, 11 Feb 2017 16:32:30 +0800 Subject: [PATCH 01/15] implement Request Rate limit, this feature is options --- global.json | 2 +- .../Configuration/Builder/ReRouteBuilder.cs | 18 ++- .../Creator/FileOcelotConfigurationCreator.cs | 21 ++- .../File/FileRateLimitOptions.cs | 66 ++++++++ src/Ocelot/Configuration/File/FileReRoute.cs | 2 + src/Ocelot/Configuration/QoSOptions.cs | 3 - src/Ocelot/Configuration/RateLimitOptions.cs | 76 +++++++++ src/Ocelot/Configuration/ReRoute.cs | 6 +- .../ServiceCollectionExtensions.cs | 2 + .../Middleware/OcelotMiddlewareExtensions.cs | 4 + .../RateLimit/ClientRateLimitProcessor.cs | 36 +++++ src/Ocelot/RateLimit/ClientRequestIdentity.cs | 11 ++ .../RateLimit/IRateLimitCounterHandler.cs | 15 ++ .../MemoryCacheRateLimitCounterHandler.cs | 45 ++++++ .../Middleware/ClientRateLimitMiddleware.cs | 142 +++++++++++++++++ .../RateLimitMiddlewareExtensions.cs | 16 ++ src/Ocelot/RateLimit/RateLimitCore.cs | 123 +++++++++++++++ src/Ocelot/RateLimit/RateLimitCounter.cs | 17 ++ src/Ocelot/RateLimit/RateLimitHeaders.cs | 19 +++ test/Ocelot.AcceptanceTests/Steps.cs | 3 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.AcceptanceTests/project.json | 3 +- test/Ocelot.ManualTest/Startup.cs | 2 +- .../ClientRateLimitMiddlewareTests.cs | 146 ++++++++++++++++++ 24 files changed, 766 insertions(+), 14 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileRateLimitOptions.cs create mode 100644 src/Ocelot/Configuration/RateLimitOptions.cs create mode 100644 src/Ocelot/RateLimit/ClientRateLimitProcessor.cs create mode 100644 src/Ocelot/RateLimit/ClientRequestIdentity.cs create mode 100644 src/Ocelot/RateLimit/IRateLimitCounterHandler.cs create mode 100644 src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs create mode 100644 src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs create mode 100644 src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs create mode 100644 src/Ocelot/RateLimit/RateLimitCore.cs create mode 100644 src/Ocelot/RateLimit/RateLimitCounter.cs create mode 100644 src/Ocelot/RateLimit/RateLimitHeaders.cs create mode 100644 test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs diff --git a/global.json b/global.json index ff8d898e..616b2c4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003133" + "version": "1.0.0-preview2-003131" } } diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 568e7c68..028b89e3 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -38,7 +38,8 @@ namespace Ocelot.Configuration.Builder private int _serviceProviderPort; private bool _useQos; private QoSOptions _qosOptions; - + public bool _enableRateLimiting; + public RateLimitOptions _rateLimitOptions; public ReRouteBuilder() { @@ -236,6 +237,19 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public ReRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public ReRoute Build() { return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, @@ -244,7 +258,7 @@ namespace Ocelot.Configuration.Builder _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), - _useQos,_qosOptions); + _useQos,_qosOptions,_enableRateLimiting,_rateLimitOptions); } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index f550edac..a7244bb1 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -109,8 +109,21 @@ namespace Ocelot.Configuration.Creator var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; ReRoute reRoute; - + var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false; + RateLimitOptions rateLimitOption = null; + if (enableRateLimiting) + { + rateLimitOption = new RateLimitOptions(enableRateLimiting, fileReRoute.RateLimitOptions.ClientIdHeader, + fileReRoute.RateLimitOptions.ClientWhitelist,fileReRoute.RateLimitOptions.DisableRateLimitHeaders, + fileReRoute.RateLimitOptions.QuotaExceededMessage,fileReRoute.RateLimitOptions.RateLimitCounterPrefix, + new RateLimitRule() + { + Limit = fileReRoute.RateLimitOptions.RateLimitRule.Limit, + Period = fileReRoute.RateLimitOptions.RateLimitRule.Period, + PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.RateLimitRule.PeriodTimespan) + }); + } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, @@ -138,7 +151,8 @@ namespace Ocelot.Configuration.Creator , fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); + new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), + enableRateLimiting, rateLimitOption); } else { @@ -151,7 +165,8 @@ namespace Ocelot.Configuration.Creator fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); + new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), + enableRateLimiting, rateLimitOption); } var loadBalancer = await _loadBalanceFactory.Get(reRoute); diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs new file mode 100644 index 00000000..bc555f33 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitOptions + { + public FileRateLimitOptions() + { + RateLimitRule = new FileRateLimitRule(); + ClientWhitelist = new List(); + } + + public FileRateLimitRule RateLimitRule { get; set; } + + public List ClientWhitelist { get; set; } + + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; set; } = "ClientId"; + + /// + /// Gets or sets the policy prefix, used to compose the client policy cache key + /// + public string ClientPolicyPrefix { get; set; } = "crlp"; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; set; } = "ocelot"; + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; set; } + } + + public class FileRateLimitRule + { + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public int PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 9ab898ea..69b13e6a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -13,6 +13,7 @@ namespace Ocelot.Configuration.File AuthenticationOptions = new FileAuthenticationOptions(); FileCacheOptions = new FileCacheOptions(); QoSOptions = new FileQoSOptions(); + RateLimitOptions = new FileRateLimitOptions(); } public string DownstreamPathTemplate { get; set; } @@ -32,5 +33,6 @@ namespace Ocelot.Configuration.File public int DownstreamPort { get; set; } public FileQoSOptions QoSOptions { get; set; } public string LoadBalancer {get;set;} + public FileRateLimitOptions RateLimitOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 8584c57e..27b862af 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,8 +1,5 @@ using Polly.Timeout; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Ocelot.Configuration { diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs new file mode 100644 index 00000000..51765874 --- /dev/null +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration +{ + /// + /// RateLimit Options + /// + public class RateLimitOptions + { + public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List clientWhitelist,bool disableRateLimitHeaders, + string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule) + { + EnableRateLimiting = enbleRateLimiting; + ClientIdHeader = clientIdHeader; + ClientWhitelist = clientWhitelist; + DisableRateLimitHeaders = disableRateLimitHeaders; + QuotaExceededMessage = quotaExceededMessage; + RateLimitCounterPrefix = rateLimitCounterPrefix; + RateLimitRule = rateLimitRule; + } + + + public RateLimitRule RateLimitRule { get; private set; } + + public List ClientWhitelist { get; private set; } + + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; private set; } = "ClientId"; + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; private set; } = 429; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; private set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; private set; } = "ocelot"; + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; private set; } + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; private set; } + } + + public class RateLimitRule + { + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public TimeSpan? PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + } +} diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index c38d55d5..38e28af9 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -17,7 +17,7 @@ namespace Ocelot.Configuration string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos) + bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions) { LoadBalancerKey = loadBalancerKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; @@ -44,6 +44,8 @@ namespace Ocelot.Configuration DownstreamScheme = downstreamScheme; IsQos = isQos; QosOptions = qos; + EnableEndpointRateLimiting = enableRateLimit; + RateLimitOptions = ratelimitOptions; } public string LoadBalancerKey {get;private set;} @@ -68,5 +70,7 @@ namespace Ocelot.Configuration public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; } + public bool EnableEndpointRateLimiting { get; private set; } + public RateLimitOptions RateLimitOptions { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 0a6bd42c..e83ad30c 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -30,6 +30,7 @@ using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Responder; using Ocelot.ServiceDiscovery; +using Ocelot.RateLimit; namespace Ocelot.DependencyInjection { @@ -84,6 +85,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // 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 diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 352aa501..4d9f643c 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -11,6 +11,7 @@ using Ocelot.Request.Middleware; using Ocelot.Requester.Middleware; using Ocelot.RequestId.Middleware; using Ocelot.Responder.Middleware; +using Ocelot.RateLimit.Middleware; namespace Ocelot.Middleware { @@ -57,6 +58,9 @@ namespace Ocelot.Middleware // Then we get the downstream route information builder.UseDownstreamRouteFinderMiddleware(); + // We check whether the request is ratelimit, and if there is no continue processing + builder.UseRateLimiting(); + // Now we can look for the requestId builder.UseRequestIdMiddleware(); diff --git a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs new file mode 100644 index 00000000..38141b49 --- /dev/null +++ b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs @@ -0,0 +1,36 @@ +using Ocelot.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class ClientRateLimitProcessor + { + private readonly IRateLimitCounterHandler _counterHandler; + private readonly RateLimitCore _core; + + public ClientRateLimitProcessor(IRateLimitCounterHandler counterHandler) + { + _counterHandler = counterHandler; + _core = new RateLimitCore(_counterHandler); + } + + public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + return _core.ProcessRequest(requestIdentity, option); + } + + public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) + { + return _core.RetryAfterFrom(timestamp, rule); + } + + public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + return _core.GetRateLimitHeaders(requestIdentity, option); + } + + } +} diff --git a/src/Ocelot/RateLimit/ClientRequestIdentity.cs b/src/Ocelot/RateLimit/ClientRequestIdentity.cs new file mode 100644 index 00000000..9cceeb9e --- /dev/null +++ b/src/Ocelot/RateLimit/ClientRequestIdentity.cs @@ -0,0 +1,11 @@ +namespace Ocelot.RateLimit +{ + public class ClientRequestIdentity + { + public string ClientId { get; set; } + + public string Path { get; set; } + + public string HttpVerb { get; set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs b/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs new file mode 100644 index 00000000..cb745a44 --- /dev/null +++ b/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public interface IRateLimitCounterHandler + { + bool Exists(string id); + RateLimitCounter? Get(string id); + void Remove(string id); + void Set(string id, RateLimitCounter counter, TimeSpan expirationTime); + } +} diff --git a/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs b/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs new file mode 100644 index 00000000..9756f2ae --- /dev/null +++ b/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class MemoryCacheRateLimitCounterHandler : IRateLimitCounterHandler + { + private readonly IMemoryCache _memoryCache; + + public MemoryCacheRateLimitCounterHandler(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) + { + _memoryCache.Set(id, counter, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + RateLimitCounter counter; + return _memoryCache.TryGetValue(id, out counter); + } + + public RateLimitCounter? Get(string id) + { + RateLimitCounter counter; + if (_memoryCache.TryGetValue(id, out counter)) + { + return counter; + } + + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +} diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs new file mode 100644 index 00000000..64ac40c0 --- /dev/null +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -0,0 +1,142 @@ +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(); + _counterHandler = counterHandler; + _processor = new ClientRateLimitProcessor(counterHandler); + } + + public async Task Invoke(HttpContext context) + { + _logger.LogDebug("started calling RateLimit middleware"); + var options = DownstreamRoute.ReRoute.RateLimitOptions; + // check if rate limiting is enabled + if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting) + { + await _next.Invoke(context); + return; + } + // compute identity from request + var identity = SetIdentity(context, options); + + // check white list + if (IsWhitelisted(identity, options)) + { + await _next.Invoke(context); + 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); + return; + } + } + //set X-Rate-Limit headers for the longest period + if (!options.DisableRateLimitHeaders) + { + var headers = _processor.GetRateLimitHeaders(identity, options); + headers.Context = context; + + context.Response.OnStarting(SetRateLimitHeaders, state: headers); + } + + await _next.Invoke(context); + } + + 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 + { + Path = httpContext.Request.Path.ToString().ToLowerInvariant(), + HttpVerb = httpContext.Request.Method.ToLowerInvariant(), + ClientId = clientId, + }; + } + + public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + if (option.ClientWhitelist != null && 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.UpstreamTemplate }, 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; + } + + } +} + + diff --git a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs new file mode 100644 index 00000000..b27d6d9d --- /dev/null +++ b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Builder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit.Middleware +{ + public static class RateLimitMiddlewareExtensions + { + public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/RateLimit/RateLimitCore.cs b/src/Ocelot/RateLimit/RateLimitCore.cs new file mode 100644 index 00000000..39904b06 --- /dev/null +++ b/src/Ocelot/RateLimit/RateLimitCore.cs @@ -0,0 +1,123 @@ +using Ocelot.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class RateLimitCore + { + private readonly IRateLimitCounterHandler _counterHandler; + private static readonly object _processLocker = new object(); + + public RateLimitCore(IRateLimitCounterHandler counterStore) + { + _counterHandler = counterStore; + } + + public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var counter = new RateLimitCounter + { + Timestamp = DateTime.UtcNow, + TotalRequests = 1 + }; + var rule = option.RateLimitRule; + + var counterId = ComputeCounterKey(requestIdentity, option); + + // serial reads and writes + lock (_processLocker) + { + var entry = _counterHandler.Get(counterId); + if (entry.HasValue) + { + // entry has not expired + if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) + { + // increment request count + var totalRequests = entry.Value.TotalRequests + 1; + + // deep copy + counter = new RateLimitCounter + { + Timestamp = entry.Value.Timestamp, + TotalRequests = totalRequests + }; + } + } + + // stores: id (string) - timestamp (datetime) - total_requests (long) + _counterHandler.Set(counterId, counter, rule.PeriodTimespan.Value); + } + + return counter; + } + + public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var rule = option.RateLimitRule; + var headers = new RateLimitHeaders(); + var counterId = ComputeCounterKey(requestIdentity, option); + var entry = _counterHandler.Get(counterId); + if (entry.HasValue) + { + headers.Reset = (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + headers.Limit = rule.Period; + headers.Remaining = (rule.Limit - entry.Value.TotalRequests).ToString(); + } + else + { + headers.Reset = (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + headers.Limit = rule.Period; + headers.Remaining = rule.Limit.ToString(); + } + + return headers; + throw new NotImplementedException(); + } + + public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; + + var idBytes = System.Text.Encoding.UTF8.GetBytes(key); + + byte[] hashBytes; + + using (var algorithm = System.Security.Cryptography.SHA1.Create()) + { + hashBytes = algorithm.ComputeHash(idBytes); + } + + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + + public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) + { + var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); + var retryAfter = Convert.ToInt32(rule.PeriodTimespan.Value.TotalSeconds); + retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; + return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + public TimeSpan ConvertToTimeSpan(string timeSpan) + { + var l = timeSpan.Length - 1; + var value = timeSpan.Substring(0, l); + var type = timeSpan.Substring(l, 1); + + switch (type) + { + case "d": return TimeSpan.FromDays(double.Parse(value)); + case "h": return TimeSpan.FromHours(double.Parse(value)); + case "m": return TimeSpan.FromMinutes(double.Parse(value)); + case "s": return TimeSpan.FromSeconds(double.Parse(value)); + default: throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); + } + } + + } +} diff --git a/src/Ocelot/RateLimit/RateLimitCounter.cs b/src/Ocelot/RateLimit/RateLimitCounter.cs new file mode 100644 index 00000000..f0133b25 --- /dev/null +++ b/src/Ocelot/RateLimit/RateLimitCounter.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + /// + /// Stores the initial access time and the numbers of calls made from that point + /// + public struct RateLimitCounter + { + public DateTime Timestamp { get; set; } + + public long TotalRequests { get; set; } + } +} diff --git a/src/Ocelot/RateLimit/RateLimitHeaders.cs b/src/Ocelot/RateLimit/RateLimitHeaders.cs new file mode 100644 index 00000000..a7bd4ae6 --- /dev/null +++ b/src/Ocelot/RateLimit/RateLimitHeaders.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class RateLimitHeaders + { + public HttpContext Context { get; set; } + + public string Limit { get; set; } + + public string Remaining { get; set; } + + public string Reset { get; set; } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 9b5faa04..92ab6daf 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -11,6 +11,7 @@ using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Ocelot.Configuration.File; @@ -100,7 +101,7 @@ namespace Ocelot.AcceptanceTests }) .WithDictionaryHandle(); }; - + s.AddMemoryCache(); s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index b155bee6..a7a48f2a 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"RateLimitRule":{"Period":null,"PeriodTimespan":0,"Limit":0},"ClientWhitelist":[],"ClientIdHeader":"ClientId","ClientPolicyPrefix":"crlp","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","EnableRateLimiting":false,"DisableRateLimitHeaders":false}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index f1aa378b..5457280f 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -33,7 +33,8 @@ "Microsoft.NETCore.App": "1.1.0", "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", - "Consul": "0.7.2.1" + "Consul": "0.7.2.1", + "Microsoft.Extensions.Caching.Memory": "1.1.0" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index 70448fcf..2d8b7653 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -37,7 +37,7 @@ namespace Ocelot.ManualTest }) .WithDictionaryHandle(); }; - + services.AddMemoryCache(); services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(); diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs new file mode 100644 index 00000000..9d826305 --- /dev/null +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -0,0 +1,146 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Infrastructure.RequestData; +using Ocelot.RateLimit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Logging; +using System.IO; +using Ocelot.RateLimit.Middleware; +using Ocelot.DownstreamRouteFinder; +using Ocelot.Responses; +using Xunit; +using TestStack.BDDfy; +using Ocelot.Configuration.Builder; +using Shouldly; + +namespace Ocelot.UnitTests.RateLimit +{ + public class ClientRateLimitMiddlewareTests + { + private readonly Mock _scopedRepository; + private readonly Mock _counterHanlder; + private readonly string _url; + private readonly TestServer _server; + private readonly HttpClient _client; + private HttpResponseMessage _result; + private OkResponse _downstreamRoute; + private int responseStatusCode; + + public ClientRateLimitMiddlewareTests() + { + _url = "http://localhost:51879/api/ClientRateLimit"; + _scopedRepository = new Mock(); + var builder = new WebHostBuilder() + .ConfigureServices(x => + { + x.AddSingleton(); + x.AddLogging(); + x.AddMemoryCache(); + x.AddSingleton(); + x.AddSingleton(_scopedRepository.Object); + }) + .UseUrls(_url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(_url) + .Configure(app => + { + app.UseRateLimiting(); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("This is ratelimit test"); + }); + }); + + _server = new TestServer(builder); + _client = _server.CreateClient(); + } + + + [Fact] + public void should_call_middleware_and_ratelimiting() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenresponseStatusCodeIs429()) + .BDDfy(); + } + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .When(x => x.WhenICallTheMiddlewareWithWhiteClient()) + .Then(x => x.ThenresponseStatusCodeIs200()) + .BDDfy(); + } + + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void WhenICallTheMiddleware() + { + var clientId = "ocelotclient1"; + // Act + for (int i = 0; i <10; i++) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + request.Headers.Add("ClientId", clientId); + + var response = _client.SendAsync(request); + responseStatusCode = (int)response.Result.StatusCode; + } + + } + + private void WhenICallTheMiddlewareWithWhiteClient() + { + var clientId = "ocelotclient2"; + // Act + for (int i = 0; i < 10; i++) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + request.Headers.Add("ClientId", clientId); + + var response = _client.SendAsync(request); + responseStatusCode = (int)response.Result.StatusCode; + } + } + + private void ThenresponseStatusCodeIs429() + { + responseStatusCode.ShouldBe(429); + } + + private void ThenresponseStatusCodeIs200() + { + responseStatusCode.ShouldBe(200); + } + } +} From 9b06afc781bc023ea0eda3ee124255f9b0b78cbd Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sat, 11 Feb 2017 20:52:20 +0800 Subject: [PATCH 02/15] refactor ratelimit config --- global.json | 2 +- .../Creator/FileOcelotConfigurationCreator.cs | 12 +++---- .../File/FileGlobalConfiguration.cs | 4 +++ .../File/FileRateLimitOptions.cs | 33 +----------------- .../Configuration/File/FileRateLimitRule.cs | 34 +++++++++++++++++++ src/Ocelot/Configuration/File/FileReRoute.cs | 4 +-- src/Ocelot/Configuration/RateLimitOptions.cs | 1 - .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../ClientRateLimitMiddlewareTests.cs | 1 - 9 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileRateLimitRule.cs diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index a7244bb1..ae8cc047 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -114,14 +114,14 @@ namespace Ocelot.Configuration.Creator RateLimitOptions rateLimitOption = null; if (enableRateLimiting) { - rateLimitOption = new RateLimitOptions(enableRateLimiting, fileReRoute.RateLimitOptions.ClientIdHeader, - fileReRoute.RateLimitOptions.ClientWhitelist,fileReRoute.RateLimitOptions.DisableRateLimitHeaders, - fileReRoute.RateLimitOptions.QuotaExceededMessage,fileReRoute.RateLimitOptions.RateLimitCounterPrefix, + rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, + fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, + globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, new RateLimitRule() { - Limit = fileReRoute.RateLimitOptions.RateLimitRule.Limit, - Period = fileReRoute.RateLimitOptions.RateLimitRule.Period, - PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.RateLimitRule.PeriodTimespan) + Limit = fileReRoute.RateLimitOptions.Limit, + Period = fileReRoute.RateLimitOptions.Period, + PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan) }); } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index f414bc83..2264bee9 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -5,8 +5,12 @@ public FileGlobalConfiguration() { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); + RateLimitOptions = new FileRateLimitOptions(); } public string RequestIdKey { get; set; } + public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} + + public FileRateLimitOptions RateLimitOptions { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index bc555f33..e685ee56 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -7,26 +7,12 @@ namespace Ocelot.Configuration.File { public class FileRateLimitOptions { - public FileRateLimitOptions() - { - RateLimitRule = new FileRateLimitRule(); - ClientWhitelist = new List(); - } - - public FileRateLimitRule RateLimitRule { get; set; } - - public List ClientWhitelist { get; set; } /// /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// public string ClientIdHeader { get; set; } = "ClientId"; - /// - /// Gets or sets the policy prefix, used to compose the client policy cache key - /// - public string ClientPolicyPrefix { get; set; } = "crlp"; - /// /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. /// If none specified the default will be: @@ -39,28 +25,11 @@ namespace Ocelot.Configuration.File /// public string RateLimitCounterPrefix { get; set; } = "ocelot"; - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; set; } - /// /// Disables X-Rate-Limit and Rety-After headers /// public bool DisableRateLimitHeaders { get; set; } } - public class FileRateLimitRule - { - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } - - public int PeriodTimespan { get; set; } - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; set; } - } + } diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs new file mode 100644 index 00000000..58d171de --- /dev/null +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + + public class FileRateLimitRule + { + public FileRateLimitRule() + { + ClientWhitelist = new List(); + } + + public List ClientWhitelist { get; set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public int PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 69b13e6a..f3658cbb 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -13,7 +13,7 @@ namespace Ocelot.Configuration.File AuthenticationOptions = new FileAuthenticationOptions(); FileCacheOptions = new FileCacheOptions(); QoSOptions = new FileQoSOptions(); - RateLimitOptions = new FileRateLimitOptions(); + RateLimitOptions = new FileRateLimitRule(); } public string DownstreamPathTemplate { get; set; } @@ -33,6 +33,6 @@ namespace Ocelot.Configuration.File public int DownstreamPort { get; set; } public FileQoSOptions QoSOptions { get; set; } public string LoadBalancer {get;set;} - public FileRateLimitOptions RateLimitOptions { get; set; } + public FileRateLimitRule RateLimitOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index 51765874..7d4d0f4a 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -22,7 +22,6 @@ namespace Ocelot.Configuration RateLimitRule = rateLimitRule; } - public RateLimitRule RateLimitRule { get; private set; } public List ClientWhitelist { get; private set; } diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index a7a48f2a..8626f7c1 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"RateLimitRule":{"Period":null,"PeriodTimespan":0,"Limit":0},"ClientWhitelist":[],"ClientIdHeader":"ClientId","ClientPolicyPrefix":"crlp","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","EnableRateLimiting":false,"DisableRateLimitHeaders":false}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 9d826305..dd1dc0e0 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -26,7 +26,6 @@ namespace Ocelot.UnitTests.RateLimit public class ClientRateLimitMiddlewareTests { private readonly Mock _scopedRepository; - private readonly Mock _counterHanlder; private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; From e1f16c2be10550adf63c9047fd94fb5e368d6361 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sun, 12 Feb 2017 15:49:21 +0800 Subject: [PATCH 03/15] add ratelimit acceptance test --- global.json | 2 +- .../Creator/FileOcelotConfigurationCreator.cs | 2 +- .../File/FileRateLimitOptions.cs | 6 +- src/Ocelot/Configuration/RateLimitOptions.cs | 9 +- ...DistributedCacheRateLimitCounterHanlder.cs | 45 ++++ .../ClientRateLimitTests.cs | 208 ++++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 18 ++ .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../ClientRateLimitMiddlewareTests.cs | 5 +- 9 files changed, 286 insertions(+), 11 deletions(-) create mode 100644 src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs create mode 100644 test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs diff --git a/global.json b/global.json index ff8d898e..616b2c4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003133" + "version": "1.0.0-preview2-003131" } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index ae8cc047..c5eababe 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -122,7 +122,7 @@ namespace Ocelot.Configuration.Creator Limit = fileReRoute.RateLimitOptions.Limit, Period = fileReRoute.RateLimitOptions.Period, PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan) - }); + }, globalConfiguration.RateLimitOptions.HttpStatusCode); } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index e685ee56..655b2442 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -7,7 +7,6 @@ namespace Ocelot.Configuration.File { public class FileRateLimitOptions { - /// /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// @@ -29,6 +28,11 @@ namespace Ocelot.Configuration.File /// Disables X-Rate-Limit and Rety-After headers /// public bool DisableRateLimitHeaders { get; set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; private set; } = 429; } diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index 7d4d0f4a..d5c68d23 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -11,7 +11,7 @@ namespace Ocelot.Configuration public class RateLimitOptions { public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List clientWhitelist,bool disableRateLimitHeaders, - string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule) + string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode) { EnableRateLimiting = enbleRateLimiting; ClientIdHeader = clientIdHeader; @@ -20,6 +20,7 @@ namespace Ocelot.Configuration QuotaExceededMessage = quotaExceededMessage; RateLimitCounterPrefix = rateLimitCounterPrefix; RateLimitRule = rateLimitRule; + HttpStatusCode = httpStatusCode; } public RateLimitRule RateLimitRule { get; private set; } @@ -29,12 +30,12 @@ namespace Ocelot.Configuration /// /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// - public string ClientIdHeader { get; private set; } = "ClientId"; + public string ClientIdHeader { get; private set; } /// /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) /// - public int HttpStatusCode { get; private set; } = 429; + public int HttpStatusCode { get; private set; } /// /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. @@ -46,7 +47,7 @@ namespace Ocelot.Configuration /// /// Gets or sets the counter prefix, used to compose the rate limit counter cache key /// - public string RateLimitCounterPrefix { get; private set; } = "ocelot"; + public string RateLimitCounterPrefix { get; private set; } /// /// Enables endpoint rate limiting based URL path and HTTP verb diff --git a/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs b/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs new file mode 100644 index 00000000..1db8f334 --- /dev/null +++ b/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Caching.Distributed; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class DistributedCacheRateLimitCounterHanlder : IRateLimitCounterHandler + { + private readonly IDistributedCache _memoryCache; + + public DistributedCacheRateLimitCounterHanlder(IDistributedCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) + { + _memoryCache.SetString(id, JsonConvert.SerializeObject(counter), new DistributedCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + var stored = _memoryCache.GetString(id); + return !string.IsNullOrEmpty(stored); + } + + public RateLimitCounter? Get(string id) + { + var stored = _memoryCache.GetString(id); + if (!string.IsNullOrEmpty(stored)) + { + return JsonConvert.DeserializeObject(stored); + } + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs new file mode 100644 index 00000000..2e8c3c90 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -0,0 +1,208 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ClientRateLimitTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private int _counterOne; + + + public ClientRateLimitTests() + { + _steps = new Steps(); + } + + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + + [Fact] + public void should_call_withratelimiting() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/ClientRateLimit", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamTemplate = "/api/ClientRateLimit", + UpstreamHttpMethod = "Get", + RequestIdKey = _steps.RequestIdKey, + + RateLimitOptions = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List(), + Limit = 3, + Period = "1s", + PeriodTimespan = 100 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "" + }, + RequestIdKey ="oceclientrequest" + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 5)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(429)) + .BDDfy(); + } + + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/ClientRateLimit", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamTemplate = "/api/ClientRateLimit", + UpstreamHttpMethod = "Get", + RequestIdKey = _steps.RequestIdKey, + + RateLimitOptions = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List() { "ocelotclient1"}, + Limit = 3, + Period = "1s", + PeriodTimespan = 100 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "" + }, + RequestIdKey = "oceclientrequest" + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .BDDfy(); + } + + + private void GivenThereIsAServiceRunningOn(string url) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(context => + { + _counterOne++; + context.Response.StatusCode = 200; + context.Response.WriteAsync(_counterOne.ToString()); + return Task.CompletedTask; + }); + }) + .Build(); + + _builder.Start(); + } + + //private void GetApiRateLimait(string url) + //{ + // var clientId = "ocelotclient1"; + // var request = new HttpRequestMessage(new HttpMethod("GET"), url); + // request.Headers.Add("ClientId", clientId); + + // var response = _client.SendAsync(request); + // responseStatusCode = (int)response.Result.StatusCode; + // } + + //} + + //public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + //{ + // var clientId = "ocelotclient1"; + // var tasks = new Task[times]; + + // for (int i = 0; i < times; i++) + // { + // var urlCopy = url; + // tasks[i] = GetForServiceDiscoveryTest(urlCopy); + // Thread.Sleep(_random.Next(40, 60)); + // } + + // Task.WaitAll(tasks); + //} + + //private void WhenICallTheMiddlewareWithWhiteClient() + //{ + // var clientId = "ocelotclient2"; + // // Act + // for (int i = 0; i < 2; i++) + // { + // var request = new HttpRequestMessage(new HttpMethod("GET"), apiRateLimitPath); + // request.Headers.Add("ClientId", clientId); + + // var response = _client.SendAsync(request); + // responseStatusCode = (int)response.Result.StatusCode; + // } + //} + + //private void ThenresponseStatusCodeIs429() + //{ + // responseStatusCode.ShouldBe(429); + //} + + //private void ThenresponseStatusCodeIs200() + //{ + // responseStatusCode.ShouldBe(200); + //} + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 92ab6daf..5981c343 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -184,6 +184,17 @@ namespace Ocelot.AcceptanceTests count.ShouldBeGreaterThan(0); } + public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times) + { + for (int i = 0; i < times; i++) + { + var clientId = "ocelotclient1"; + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + request.Headers.Add("ClientId", clientId); + _response = _ocelotClient.SendAsync(request).Result; + } + } + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); @@ -211,6 +222,13 @@ namespace Ocelot.AcceptanceTests _response.StatusCode.ShouldBe(expectedHttpStatusCode); } + + public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) + { + var responseStatusCode = (int)_response.StatusCode; + responseStatusCode.ShouldBe(expectedHttpStatusCode); + } + public void Dispose() { _ocelotClient?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 8626f7c1..af289aab 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index dd1dc0e0..11bca113 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -29,7 +29,6 @@ namespace Ocelot.UnitTests.RateLimit private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _downstreamRoute; private int responseStatusCode; @@ -71,7 +70,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) @@ -85,7 +84,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) From 659d12478031b8469c5342c1a580621555aba547 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sun, 12 Feb 2017 15:50:21 +0800 Subject: [PATCH 04/15] reset sdk version --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } From 2fa6e66dd46422232323944df27d5227ff535044 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Mon, 13 Feb 2017 08:29:29 +0800 Subject: [PATCH 05/15] refactor code --- .../Creator/FileOcelotConfigurationCreator.cs | 8 +-- .../File/FileRateLimitOptions.cs | 2 +- .../Configuration/File/FileRateLimitRule.cs | 2 +- src/Ocelot/Configuration/RateLimitOptions.cs | 21 ++++-- .../ServiceCollectionExtensions.cs | 2 +- .../RateLimit/ClientRateLimitProcessor.cs | 7 +- src/Ocelot/RateLimit/ClientRequestIdentity.cs | 13 +++- .../Middleware/ClientRateLimitMiddleware.cs | 20 +++--- src/Ocelot/RateLimit/RateLimitCore.cs | 68 ++++++++++--------- src/Ocelot/RateLimit/RateLimitCounter.cs | 10 ++- src/Ocelot/RateLimit/RateLimitHeaders.cs | 16 +++-- .../ClientRateLimitTests.cs | 63 +++-------------- test/Ocelot.AcceptanceTests/Steps.cs | 1 - .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.AcceptanceTests/project.json | 4 +- test/Ocelot.ManualTest/Startup.cs | 1 - .../ClientRateLimitMiddlewareTests.cs | 13 ++-- test/Ocelot.UnitTests/project.json | 4 +- 18 files changed, 119 insertions(+), 138 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c5eababe..646af96c 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -117,12 +117,8 @@ namespace Ocelot.Configuration.Creator rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, - new RateLimitRule() - { - Limit = fileReRoute.RateLimitOptions.Limit, - Period = fileReRoute.RateLimitOptions.Period, - PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan) - }, globalConfiguration.RateLimitOptions.HttpStatusCode); + new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit) + , globalConfiguration.RateLimitOptions.HttpStatusCode); } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index 655b2442..afb3fab5 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -32,7 +32,7 @@ namespace Ocelot.Configuration.File /// /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) /// - public int HttpStatusCode { get; private set; } = 429; + public int HttpStatusCode { get; set; } = 429; } diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs index 58d171de..727a9e82 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitRule.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -25,7 +25,7 @@ namespace Ocelot.Configuration.File /// public string Period { get; set; } - public int PeriodTimespan { get; set; } + public double PeriodTimespan { get; set; } /// /// Maximum number of requests that a client can make in a defined period /// diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index d5c68d23..85a3bf78 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -15,7 +15,7 @@ namespace Ocelot.Configuration { EnableRateLimiting = enbleRateLimiting; ClientIdHeader = clientIdHeader; - ClientWhitelist = clientWhitelist; + ClientWhitelist = clientWhitelist?? new List(); DisableRateLimitHeaders = disableRateLimitHeaders; QuotaExceededMessage = quotaExceededMessage; RateLimitCounterPrefix = rateLimitCounterPrefix; @@ -62,15 +62,22 @@ namespace Ocelot.Configuration public class RateLimitRule { - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } + public RateLimitRule(string period, TimeSpan periodTimespan, long limit) + { + Period = period; + PeriodTimespan = periodTimespan; + Limit = limit; + } - public TimeSpan? PeriodTimespan { get; set; } + /// + /// Rate limit period as in 1s, 1m, 1h,1d + /// + public string Period { get; private set; } + + public TimeSpan PeriodTimespan { get; private set; } /// /// Maximum number of requests that a client can make in a defined period /// - public long Limit { get; set; } + public long Limit { get; private set; } } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index e83ad30c..446720ca 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -91,7 +91,7 @@ namespace Ocelot.DependencyInjection // could maybe use a scoped data repository services.AddSingleton(); services.AddScoped(); - + services.AddMemoryCache(); return services; } } diff --git a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs index 38141b49..a2ee1202 100644 --- a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs +++ b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; using System; using System.Collections.Generic; using System.Linq; @@ -27,9 +28,9 @@ namespace Ocelot.RateLimit return _core.RetryAfterFrom(timestamp, rule); } - public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) { - return _core.GetRateLimitHeaders(requestIdentity, option); + return _core.GetRateLimitHeaders(context, requestIdentity, option); } } diff --git a/src/Ocelot/RateLimit/ClientRequestIdentity.cs b/src/Ocelot/RateLimit/ClientRequestIdentity.cs index 9cceeb9e..a27bc994 100644 --- a/src/Ocelot/RateLimit/ClientRequestIdentity.cs +++ b/src/Ocelot/RateLimit/ClientRequestIdentity.cs @@ -2,10 +2,17 @@ { public class ClientRequestIdentity { - public string ClientId { get; set; } + public ClientRequestIdentity(string clientId, string path, string httpverb) + { + ClientId = clientId; + Path = path; + HttpVerb = httpverb; + } - public string Path { get; set; } + public string ClientId { get; private set; } - public string HttpVerb { get; set; } + public string Path { get; private set; } + + public string HttpVerb { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index 64ac40c0..4dbaeed1 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -72,9 +72,7 @@ namespace Ocelot.RateLimit.Middleware //set X-Rate-Limit headers for the longest period if (!options.DisableRateLimitHeaders) { - var headers = _processor.GetRateLimitHeaders(identity, options); - headers.Context = context; - + var headers = _processor.GetRateLimitHeaders( context,identity, options); context.Response.OnStarting(SetRateLimitHeaders, state: headers); } @@ -89,21 +87,19 @@ namespace Ocelot.RateLimit.Middleware clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); } - return new ClientRequestIdentity - { - Path = httpContext.Request.Path.ToString().ToLowerInvariant(), - HttpVerb = httpContext.Request.Method.ToLowerInvariant(), - ClientId = clientId, - }; - } + return new ClientRequestIdentity( + clientId, + httpContext.Request.Path.ToString().ToLowerInvariant(), + httpContext.Request.Method.ToLowerInvariant() + ); + } public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) { - if (option.ClientWhitelist != null && option.ClientWhitelist.Contains(requestIdentity.ClientId)) + if (option.ClientWhitelist.Contains(requestIdentity.ClientId)) { return true; } - return false; } diff --git a/src/Ocelot/RateLimit/RateLimitCore.cs b/src/Ocelot/RateLimit/RateLimitCore.cs index 39904b06..07627f8d 100644 --- a/src/Ocelot/RateLimit/RateLimitCore.cs +++ b/src/Ocelot/RateLimit/RateLimitCore.cs @@ -1,8 +1,11 @@ -using Ocelot.Configuration; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Ocelot.RateLimit @@ -19,11 +22,7 @@ namespace Ocelot.RateLimit public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) { - var counter = new RateLimitCounter - { - Timestamp = DateTime.UtcNow, - TotalRequests = 1 - }; + RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1); var rule = option.RateLimitRule; var counterId = ComputeCounterKey(requestIdentity, option); @@ -35,59 +34,57 @@ namespace Ocelot.RateLimit if (entry.HasValue) { // entry has not expired - if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) + if (entry.Value.Timestamp + rule.PeriodTimespan >= DateTime.UtcNow) { // increment request count var totalRequests = entry.Value.TotalRequests + 1; // deep copy - counter = new RateLimitCounter - { - Timestamp = entry.Value.Timestamp, - TotalRequests = totalRequests - }; + counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests); + } } - // stores: id (string) - timestamp (datetime) - total_requests (long) - _counterHandler.Set(counterId, counter, rule.PeriodTimespan.Value); + _counterHandler.Set(counterId, counter, rule.PeriodTimespan); } return counter; } - public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) { var rule = option.RateLimitRule; - var headers = new RateLimitHeaders(); + RateLimitHeaders headers = null; var counterId = ComputeCounterKey(requestIdentity, option); var entry = _counterHandler.Get(counterId); if (entry.HasValue) { - headers.Reset = (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); - headers.Limit = rule.Period; - headers.Remaining = (rule.Limit - entry.Value.TotalRequests).ToString(); - } + headers = new RateLimitHeaders(context, rule.Period, + (rule.Limit - entry.Value.TotalRequests).ToString(), + (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo) + ); + } else { - headers.Reset = (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); - headers.Limit = rule.Period; - headers.Remaining = rule.Limit.ToString(); + headers = new RateLimitHeaders(context, + rule.Period, + rule.Limit.ToString(), + (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)); + } return headers; - throw new NotImplementedException(); - } + } public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) { var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; - var idBytes = System.Text.Encoding.UTF8.GetBytes(key); + var idBytes = Encoding.UTF8.GetBytes(key); byte[] hashBytes; - using (var algorithm = System.Security.Cryptography.SHA1.Create()) + using (var algorithm = SHA1.Create()) { hashBytes = algorithm.ComputeHash(idBytes); } @@ -98,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.Value.TotalSeconds); + var retryAfter = Convert.ToInt32(rule.PeriodTimespan.TotalSeconds); retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); } @@ -111,11 +108,16 @@ namespace Ocelot.RateLimit switch (type) { - case "d": return TimeSpan.FromDays(double.Parse(value)); - case "h": return TimeSpan.FromHours(double.Parse(value)); - case "m": return TimeSpan.FromMinutes(double.Parse(value)); - case "s": return TimeSpan.FromSeconds(double.Parse(value)); - default: throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); + case "d": + return TimeSpan.FromDays(double.Parse(value)); + case "h": + return TimeSpan.FromHours(double.Parse(value)); + case "m": + return TimeSpan.FromMinutes(double.Parse(value)); + case "s": + return TimeSpan.FromSeconds(double.Parse(value)); + default: + throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); } } diff --git a/src/Ocelot/RateLimit/RateLimitCounter.cs b/src/Ocelot/RateLimit/RateLimitCounter.cs index f0133b25..42dd03b7 100644 --- a/src/Ocelot/RateLimit/RateLimitCounter.cs +++ b/src/Ocelot/RateLimit/RateLimitCounter.cs @@ -10,8 +10,14 @@ namespace Ocelot.RateLimit /// public struct RateLimitCounter { - public DateTime Timestamp { get; set; } + public RateLimitCounter(DateTime timestamp, long totalRequest) + { + Timestamp = timestamp; + TotalRequests = totalRequest; + } - public long TotalRequests { get; set; } + public DateTime Timestamp { get; private set; } + + public long TotalRequests { get; private set; } } } diff --git a/src/Ocelot/RateLimit/RateLimitHeaders.cs b/src/Ocelot/RateLimit/RateLimitHeaders.cs index a7bd4ae6..909f656d 100644 --- a/src/Ocelot/RateLimit/RateLimitHeaders.cs +++ b/src/Ocelot/RateLimit/RateLimitHeaders.cs @@ -8,12 +8,20 @@ namespace Ocelot.RateLimit { public class RateLimitHeaders { - public HttpContext Context { get; set; } + public RateLimitHeaders(HttpContext context, string limit, string remaining, string reset) + { + Context = context; + Limit = limit; + Remaining = remaining; + Reset = reset; + } - public string Limit { get; set; } + public HttpContext Context { get; private set; } - public string Remaining { get; set; } + public string Limit { get; private set; } - public string Reset { get; set; } + public string Remaining { get; private set; } + + public string Reset { get; private set; } } } diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 2e8c3c90..dc2b2b31 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -67,7 +67,9 @@ namespace Ocelot.AcceptanceTests ClientIdHeader = "ClientId", DisableRateLimitHeaders = false, QuotaExceededMessage = "", - RateLimitCounterPrefix = "" + RateLimitCounterPrefix = "", + HttpStatusCode = 428 + }, RequestIdKey ="oceclientrequest" } @@ -76,8 +78,12 @@ namespace Ocelot.AcceptanceTests this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 5)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(429)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) .BDDfy(); } @@ -154,55 +160,6 @@ namespace Ocelot.AcceptanceTests _builder.Start(); } - //private void GetApiRateLimait(string url) - //{ - // var clientId = "ocelotclient1"; - // var request = new HttpRequestMessage(new HttpMethod("GET"), url); - // request.Headers.Add("ClientId", clientId); - - // var response = _client.SendAsync(request); - // responseStatusCode = (int)response.Result.StatusCode; - // } - - //} - - //public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) - //{ - // var clientId = "ocelotclient1"; - // var tasks = new Task[times]; - - // for (int i = 0; i < times; i++) - // { - // var urlCopy = url; - // tasks[i] = GetForServiceDiscoveryTest(urlCopy); - // Thread.Sleep(_random.Next(40, 60)); - // } - - // Task.WaitAll(tasks); - //} - - //private void WhenICallTheMiddlewareWithWhiteClient() - //{ - // var clientId = "ocelotclient2"; - // // Act - // for (int i = 0; i < 2; i++) - // { - // var request = new HttpRequestMessage(new HttpMethod("GET"), apiRateLimitPath); - // request.Headers.Add("ClientId", clientId); - - // var response = _client.SendAsync(request); - // responseStatusCode = (int)response.Result.StatusCode; - // } - //} - - //private void ThenresponseStatusCodeIs429() - //{ - // responseStatusCode.ShouldBe(429); - //} - - //private void ThenresponseStatusCodeIs200() - //{ - // responseStatusCode.ShouldBe(200); - //} + } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5981c343..ebced901 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -101,7 +101,6 @@ namespace Ocelot.AcceptanceTests }) .WithDictionaryHandle(); }; - s.AddMemoryCache(); s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index af289aab..0a65abc2 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 5457280f..2dd3094e 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -23,7 +23,6 @@ "Microsoft.AspNetCore.Http": "1.1.0", "Microsoft.DotNet.InternalAbstractions": "1.0.0", "Ocelot": "0.0.0-dev", - "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Ocelot.ManualTest": "0.0.0-dev", "Microsoft.AspNetCore.TestHost": "1.1.0", @@ -34,7 +33,8 @@ "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", "Consul": "0.7.2.1", - "Microsoft.Extensions.Caching.Memory": "1.1.0" + "Microsoft.Extensions.Caching.Memory": "1.1.0", + "xunit": "2.2.0-rc1-build3507" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index 2d8b7653..767aed43 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -37,7 +37,6 @@ namespace Ocelot.ManualTest }) .WithDictionaryHandle(); }; - services.AddMemoryCache(); services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(); diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 11bca113..72f66faa 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -20,6 +20,7 @@ using Xunit; using TestStack.BDDfy; using Ocelot.Configuration.Builder; using Shouldly; +using Ocelot.Configuration; namespace Ocelot.UnitTests.RateLimit { @@ -70,11 +71,13 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .When(x => x.WhenICallTheMiddleware()) + .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) + .Then(x => x.ThenresponseStatusCodeIs200()) + .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) .Then(x => x.ThenresponseStatusCodeIs429()) .BDDfy(); } @@ -84,7 +87,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) @@ -102,11 +105,11 @@ namespace Ocelot.UnitTests.RateLimit .Returns(_downstreamRoute); } - private void WhenICallTheMiddleware() + private void WhenICallTheMiddlewareMultipleTime(int times) { var clientId = "ocelotclient1"; // Act - for (int i = 0; i <10; i++) + for (int i = 0; i < times; i++) { var request = new HttpRequestMessage(new HttpMethod("GET"), _url); request.Headers.Add("ClientId", clientId); diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 3151ac57..2f42c283 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -14,7 +14,6 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", "Ocelot": "0.0.0-dev", - "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Moq": "4.6.38-alpha", "Microsoft.AspNetCore.TestHost": "1.1.0", @@ -24,7 +23,8 @@ "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.DotNet.InternalAbstractions": "1.0.0" + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "xunit": "2.2.0-rc1-build3507" }, "runtimes": { "win10-x64": {}, From ab6ae8a062022f0a94b1d1a34e8797a2d4250e9b Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 15 Feb 2017 08:50:22 +0800 Subject: [PATCH 06/15] merge newest code for develop --- .../Configuration/Builder/ReRouteBuilder.cs | 129 +++------ .../Creator/FileOcelotConfigurationCreator.cs | 247 ++++++++++++------ src/Ocelot/Configuration/QoSOptions.cs | 10 +- src/Ocelot/Configuration/ReRoute.cs | 41 ++- 4 files changed, 241 insertions(+), 186 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 028b89e3..2e442701 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,23 +1,19 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { + private AuthenticationOptions _authenticationOptions; private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; private bool _isAuthenticated; - private string _authenticationProvider; - private string _authenticationProviderUrl; - private string _scopeName; - private List _additionalScopes; - private bool _requireHttps; - private string _scopeSecret; private List _configHeaderExtractorProperties; private List _claimToClaims; private Dictionary _routeClaimRequirement; @@ -26,29 +22,19 @@ namespace Ocelot.Configuration.Builder private string _requestIdHeaderKey; private bool _isCached; private CacheOptions _fileCacheOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private string _serviceDiscoveryProvider; - private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; - private int _dsPort; + private int _downstreamPort; private string _loadBalancer; - private string _serviceProviderHost; - private int _serviceProviderPort; + private ServiceProviderConfiguraion _serviceProviderConfiguraion; private bool _useQos; private QoSOptions _qosOptions; public bool _enableRateLimiting; public RateLimitOptions _rateLimitOptions; - public ReRouteBuilder() - { - _additionalScopes = new List(); - } - public ReRouteBuilder WithLoadBalancer(string loadBalancer) { - _loadBalancer = loadBalancer; + _loadBalancer = loadBalancer; return this; } @@ -64,37 +50,13 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress) - { - _serviceDiscoveryAddress = serviceDiscoveryAddress; - return this; - } - - public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) - { - _serviceDiscoveryProvider = serviceDiscoveryProvider; - return this; - } - - public ReRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - public ReRouteBuilder WithDownstreamPathTemplate(string input) { _downstreamPathTemplate = input; return this; } - public ReRouteBuilder WithUpstreamTemplate(string input) + public ReRouteBuilder WithUpstreamPathTemplate(string input) { _upstreamTemplate = input; return this; @@ -122,42 +84,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithAuthenticationProvider(string input) - { - _authenticationProvider = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderUrl(string input) - { - _authenticationProviderUrl = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderScopeName(string input) - { - _scopeName = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderAdditionalScopes(List input) - { - _additionalScopes = input; - return this; - } - - public ReRouteBuilder WithRequireHttps(bool input) - { - _requireHttps = input; - return this; - } - - public ReRouteBuilder WithScopeSecret(string input) - { - _scopeSecret = input; - return this; - } - public ReRouteBuilder WithRequestIdKey(string input) { _requestIdHeaderKey = input; @@ -202,7 +128,7 @@ namespace Ocelot.Configuration.Builder public ReRouteBuilder WithDownstreamPort(int port) { - _dsPort = port; + _downstreamPort = port; return this; } @@ -225,15 +151,15 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost) + public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion) { - _serviceProviderHost = serviceProviderHost; + _serviceProviderConfiguraion = serviceProviderConfiguraion; return this; } - public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort) + public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) { - _serviceProviderPort = serviceProviderPort; + _authenticationOptions = authenticationOptions; return this; } @@ -252,14 +178,31 @@ namespace Ocelot.Configuration.Builder public ReRoute Build() { - return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, - _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, - _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, - _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), - _useQos,_qosOptions,_enableRateLimiting,_rateLimitOptions); - + return new ReRoute( + new PathTemplate(_downstreamPathTemplate), + new PathTemplate(_upstreamTemplate), + new HttpMethod(_upstreamHttpMethod), + _upstreamTemplatePattern, + _isAuthenticated, + _authenticationOptions, + _configHeaderExtractorProperties, + _claimToClaims, + _routeClaimRequirement, + _isAuthorised, + _claimToQueries, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _downstreamScheme, + _loadBalancer, + _downstreamHost, + _downstreamPort, + _loadBalancerKey, + _serviceProviderConfiguraion, + _useQos, + _qosOptions, + _enableRateLimiting, + _rateLimitOptions); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 646af96c..55f31bfa 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -4,13 +4,14 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Requester.QoS; using Ocelot.Responses; using Ocelot.Utilities; -using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -24,11 +25,14 @@ namespace Ocelot.Configuration.Creator private const string RegExMatchEverything = ".*"; private const string RegExMatchEndString = "$"; private const string RegExIgnoreCase = "(?i)"; + private const string RegExForwardSlashOnly = "^/$"; private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; private readonly ILogger _logger; private readonly ILoadBalancerFactory _loadBalanceFactory; private readonly ILoadBalancerHouse _loadBalancerHouse; + private readonly IQoSProviderFactory _qoSProviderFactory; + private readonly IQosProviderHouse _qosProviderHouse; public FileOcelotConfigurationCreator( IOptions options, @@ -36,10 +40,14 @@ namespace Ocelot.Configuration.Creator IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger logger, ILoadBalancerFactory loadBalancerFactory, - ILoadBalancerHouse loadBalancerHouse) + ILoadBalancerHouse loadBalancerHouse, + IQoSProviderFactory qoSProviderFactory, + IQosProviderHouse qosProviderHouse) { _loadBalanceFactory = loadBalancerFactory; _loadBalancerHouse = loadBalancerHouse; + _qoSProviderFactory = qoSProviderFactory; + _qosProviderHouse = qosProviderHouse; _options = options; _configurationValidator = configurationValidator; _claimToThingConfigurationParser = claimToThingConfigurationParser; @@ -53,10 +61,6 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - /// - /// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see - /// will need a refactor at some point as its crap - /// private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); @@ -86,93 +90,179 @@ namespace Ocelot.Configuration.Creator private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { - var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + var isAuthenticated = IsAuthenticated(fileReRoute); - var upstreamTemplate = BuildUpstreamTemplate(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + var isCached = IsCached(fileReRoute); - var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; + var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); - var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; + var reRouteKey = BuildReRouteKey(fileReRoute); - var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; + var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); - var requestIdKey = globalRequestIdConfiguration - ? globalConfiguration.RequestIdKey - : fileReRoute.RequestIdKey; + var isQos = IsQoS(fileReRoute); - var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); - //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain - var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; + var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); - ReRoute reRoute; + var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false; - RateLimitOptions rateLimitOption = null; - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, - fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, - globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, - new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit) - , globalConfiguration.RateLimitOptions.HttpStatusCode); - } - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, - fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host, - serviceProviderPort); + var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); - if (isAuthenticated) - { - var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, - fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, - fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, - fileReRoute.AuthenticationOptions.ScopeSecret); + var qosOptions = BuildQoSOptions(fileReRoute); - var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } - else - { - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } - - var loadBalancer = await _loadBalanceFactory.Get(reRoute); - _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); + var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, enableRateLimiting); + + var reRoute = new ReRouteBuilder() + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(isAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(isAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(isCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithLoadBalancerKey(reRouteKey) + .WithServiceProviderConfiguraion(serviceProviderConfiguration) + .WithIsQos(isQos) + .WithQosOptions(qosOptions) + .WithEnableRateLimiting(enableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .Build(); + await SetupLoadBalancer(reRoute); + SetupQosProvider(reRoute); return reRoute; } - private string BuildUpstreamTemplate(FileReRoute reRoute) + private static RateLimitOptions BuildRateLimitOptions(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) { - var upstreamTemplate = reRoute.UpstreamTemplate; + RateLimitOptions rateLimitOption = null; + if (enableRateLimiting) + { + rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, + fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, + globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, + new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit) + , globalConfiguration.RateLimitOptions.HttpStatusCode); + } + + return rateLimitOption; + } + + private static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; + } + + private QoSOptions BuildQoSOptions(FileReRoute fileReRoute) + { + return new QoSOptionsBuilder() + .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) + .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) + .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) + .Build(); + } + + private bool IsQoS(FileReRoute fileReRoute) + { + return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; + } + + private bool IsAuthenticated(FileReRoute fileReRoute) + { + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } + + private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + + var requestIdKey = globalRequestIdConfiguration + ? globalConfiguration.RequestIdKey + : fileReRoute.RequestIdKey; + + return requestIdKey; + } + + private string BuildReRouteKey(FileReRoute fileReRoute) + { + //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain + var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; + return loadBalancerKey; + } + + private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute) + { + return new AuthenticationOptionsBuilder() + .WithProvider(fileReRoute.AuthenticationOptions?.Provider) + .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) + .WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName) + .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) + .WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes) + .WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret) + .Build(); + } + + private async Task SetupLoadBalancer(ReRoute reRoute) + { + var loadBalancer = await _loadBalanceFactory.Get(reRoute); + _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer); + } + + private void SetupQosProvider(ReRoute reRoute) + { + var loadBalancer = _qoSProviderFactory.Get(reRoute); + _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); + } + + private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) + && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + + var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + + return new ServiceProviderConfiguraionBuilder() + .WithServiceName(fileReRoute.ServiceName) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithUseServiceDiscovery(useServiceDiscovery) + .WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider) + .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithServiceDiscoveryProviderPort(serviceProviderPort) + .Build(); + } + + private string BuildUpstreamTemplatePattern(FileReRoute reRoute) + { + var upstreamTemplate = reRoute.UpstreamPathTemplate; upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); @@ -194,6 +284,11 @@ namespace Ocelot.Configuration.Creator upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); } + if (upstreamTemplate == "/") + { + return RegExForwardSlashOnly; + } + var route = reRoute.ReRouteIsCaseSensitive ? $"{upstreamTemplate}{RegExMatchEndString}" : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; @@ -201,7 +296,7 @@ namespace Ocelot.Configuration.Creator return route; } - private List GetAddThingsToRequest(Dictionary thingBeingAdded) + private List BuildAddThingsToRequest(Dictionary thingBeingAdded) { var claimsToTHings = new List(); diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 27b862af..29145888 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,11 +1,15 @@ -using Polly.Timeout; -using System; +using System; +using Polly.Timeout; namespace Ocelot.Configuration { public class QoSOptions { - public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) + public QoSOptions( + int exceptionsAllowedBeforeBreaking, + int durationofBreak, + int timeoutValue, + TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) { ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 38e28af9..bfce7c76 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,31 +1,44 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(DownstreamPathTemplate downstreamPathTemplate, - string upstreamTemplate, string upstreamHttpMethod, + public ReRoute(PathTemplate downstreamPathTemplate, + PathTemplate upstreamTemplate, + HttpMethod upstreamHttpMethod, string upstreamTemplatePattern, - bool isAuthenticated, AuthenticationOptions authenticationOptions, + bool isAuthenticated, + AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, - Dictionary routeClaimsRequirement, bool isAuthorised, + Dictionary routeClaimsRequirement, + bool isAuthorised, List claimsToQueries, - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, - string downstreamScheme, string loadBalancer, string downstreamHost, - int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions) + string requestIdKey, + bool isCached, + CacheOptions fileCacheOptions, + string downstreamScheme, + string loadBalancer, + string downstreamHost, + int downstreamPort, + string reRouteKey, + ServiceProviderConfiguraion serviceProviderConfiguraion, + bool isQos, + QoSOptions qos, + bool enableRateLimit, + RateLimitOptions ratelimitOptions) { - LoadBalancerKey = loadBalancerKey; + ReRouteKey = reRouteKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; - UpstreamTemplate = upstreamTemplate; + UpstreamPathTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; @@ -48,11 +61,11 @@ namespace Ocelot.Configuration RateLimitOptions = ratelimitOptions; } - public string LoadBalancerKey {get;private set;} - public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } - public string UpstreamTemplate { get; private set; } + public string ReRouteKey {get;private set;} + public PathTemplate DownstreamPathTemplate { get; private set; } + public PathTemplate UpstreamPathTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } - public string UpstreamHttpMethod { get; private set; } + public HttpMethod UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } public bool IsAuthorised { get; private set; } public AuthenticationOptions AuthenticationOptions { get; private set; } From f302ee77bfd2ce6d162d017ff69f1493835cc662 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 15 Feb 2017 08:54:20 +0800 Subject: [PATCH 07/15] remove bak file --- global.json | 2 +- .../FileOcelotConfigurationCreator.cs.bak | 356 ------------------ src/Ocelot/Configuration/QoSOptions.cs.bak | 35 -- src/Ocelot/Configuration/ReRoute.cs.bak | 94 ----- .../Ocelot.AcceptanceTests/configuration.json | 2 +- 5 files changed, 2 insertions(+), 487 deletions(-) delete mode 100644 src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak delete mode 100644 src/Ocelot/Configuration/QoSOptions.cs.bak delete mode 100644 src/Ocelot/Configuration/ReRoute.cs.bak diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak deleted file mode 100644 index 86cd0ff2..00000000 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Parser; -using Ocelot.Configuration.Validator; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Requester.QoS; -using Ocelot.Responses; -using Ocelot.Utilities; - -namespace Ocelot.Configuration.Creator -{ - /// - /// Register as singleton - /// - public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator - { - private readonly IOptions _options; - private readonly IConfigurationValidator _configurationValidator; - private const string RegExMatchEverything = ".*"; - private const string RegExMatchEndString = "$"; - private const string RegExIgnoreCase = "(?i)"; - private const string RegExForwardSlashOnly = "^/$"; - - private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; - private readonly ILogger _logger; - private readonly ILoadBalancerFactory _loadBalanceFactory; - private readonly ILoadBalancerHouse _loadBalancerHouse; - private readonly IQoSProviderFactory _qoSProviderFactory; - private readonly IQosProviderHouse _qosProviderHouse; - - public FileOcelotConfigurationCreator( - IOptions options, - IConfigurationValidator configurationValidator, - IClaimToThingConfigurationParser claimToThingConfigurationParser, - ILogger logger, - ILoadBalancerFactory loadBalancerFactory, - ILoadBalancerHouse loadBalancerHouse, - IQoSProviderFactory qoSProviderFactory, - IQosProviderHouse qosProviderHouse) - { - _loadBalanceFactory = loadBalancerFactory; - _loadBalancerHouse = loadBalancerHouse; - _qoSProviderFactory = qoSProviderFactory; - _qosProviderHouse = qosProviderHouse; - _options = options; - _configurationValidator = configurationValidator; - _claimToThingConfigurationParser = claimToThingConfigurationParser; - _logger = logger; - } - - public async Task> Create() - { - var config = await SetUpConfiguration(); - - return new OkResponse(config); - } - - private async Task SetUpConfiguration() - { - var response = _configurationValidator.IsValid(_options.Value); - - if (response.Data.IsError) - { - var errorBuilder = new StringBuilder(); - - foreach (var error in response.Errors) - { - errorBuilder.AppendLine(error.Message); - } - - throw new Exception($"Unable to start Ocelot..configuration, errors were {errorBuilder}"); - } - - var reRoutes = new List(); - - foreach (var reRoute in _options.Value.ReRoutes) - { - var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); - reRoutes.Add(ocelotReRoute); - } - - return new OcelotConfiguration(reRoutes); - } - - private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var isAuthenticated = IsAuthenticated(fileReRoute); - - var isAuthorised = IsAuthorised(fileReRoute); - - var isCached = IsCached(fileReRoute); - - var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); - - var reRouteKey = BuildReRouteKey(fileReRoute); - - var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); - - var isQos = IsQoS(fileReRoute); - - var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); - - var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); - -<<<<<<< HEAD - ReRoute reRoute; - - var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false; - RateLimitOptions rateLimitOption = null; - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, - fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, - globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, - new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit) - , globalConfiguration.RateLimitOptions.HttpStatusCode); - } - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; -======= - var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); - - var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); - - var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); - - var qosOptions = BuildQoSOptions(fileReRoute); - - var reRoute = new ReRouteBuilder() - .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithIsAuthenticated(isAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(isAuthorised) - .WithClaimsToQueries(claimsToQueries) - .WithRequestIdKey(requestIdKey) - .WithIsCached(isCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) - .WithDownstreamScheme(fileReRoute.DownstreamScheme) - .WithLoadBalancer(fileReRoute.LoadBalancer) - .WithDownstreamHost(fileReRoute.DownstreamHost) - .WithDownstreamPort(fileReRoute.DownstreamPort) - .WithLoadBalancerKey(reRouteKey) - .WithServiceProviderConfiguraion(serviceProviderConfiguration) - .WithIsQos(isQos) - .WithQosOptions(qosOptions) - .Build(); - - await SetupLoadBalancer(reRoute); - SetupQosProvider(reRoute); - return reRoute; - } - - private QoSOptions BuildQoSOptions(FileReRoute fileReRoute) - { - return new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) - .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) - .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) - .Build(); - } ->>>>>>> refs/remotes/origin/develop - - private bool IsQoS(FileReRoute fileReRoute) - { - return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; - } - -<<<<<<< HEAD - if (isAuthenticated) - { - var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, - fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, - fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, - fileReRoute.AuthenticationOptions.ScopeSecret); - - var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } - else - { - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } -======= - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); - } ->>>>>>> refs/remotes/origin/develop - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - return fileReRoute.FileCacheOptions.TtlSeconds > 0; - } - - private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); - - var requestIdKey = globalRequestIdConfiguration - ? globalConfiguration.RequestIdKey - : fileReRoute.RequestIdKey; - - return requestIdKey; - } - - private string BuildReRouteKey(FileReRoute fileReRoute) - { - //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain - var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; - return loadBalancerKey; - } - - private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute) - { - return new AuthenticationOptionsBuilder() - .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) - .WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName) - .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) - .WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes) - .WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret) - .Build(); - } - - private async Task SetupLoadBalancer(ReRoute reRoute) - { - var loadBalancer = await _loadBalanceFactory.Get(reRoute); - _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer); - } - - private void SetupQosProvider(ReRoute reRoute) - { - var loadBalancer = _qoSProviderFactory.Get(reRoute); - _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); - } - - private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - - return new ServiceProviderConfiguraionBuilder() - .WithServiceName(fileReRoute.ServiceName) - .WithDownstreamHost(fileReRoute.DownstreamHost) - .WithDownstreamPort(fileReRoute.DownstreamPort) - .WithUseServiceDiscovery(useServiceDiscovery) - .WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider) - .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) - .WithServiceDiscoveryProviderPort(serviceProviderPort) - .Build(); - } - - private string BuildUpstreamTemplatePattern(FileReRoute reRoute) - { - var upstreamTemplate = reRoute.UpstreamPathTemplate; - - upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); - - var placeholders = new List(); - - for (var i = 0; i < upstreamTemplate.Length; i++) - { - if (IsPlaceHolder(upstreamTemplate, i)) - { - var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); - var difference = postitionOfPlaceHolderClosingBracket - i + 1; - var variableName = upstreamTemplate.Substring(i, difference); - placeholders.Add(variableName); - } - } - - foreach (var placeholder in placeholders) - { - upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); - } - - if (upstreamTemplate == "/") - { - return RegExForwardSlashOnly; - } - - var route = reRoute.ReRouteIsCaseSensitive - ? $"{upstreamTemplate}{RegExMatchEndString}" - : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - - return route; - } - - private List BuildAddThingsToRequest(Dictionary thingBeingAdded) - { - var claimsToTHings = new List(); - - foreach (var add in thingBeingAdded) - { - var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value); - - if (claimToHeader.IsError) - { - _logger.LogCritical(new EventId(1, "Application Failed to start"), - $"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect"); - - throw new Exception(claimToHeader.Errors[0].Message); - } - claimsToTHings.Add(claimToHeader.Data); - } - - return claimsToTHings; - } - - private bool IsPlaceHolder(string upstreamTemplate, int i) - { - return upstreamTemplate[i] == '{'; - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/QoSOptions.cs.bak b/src/Ocelot/Configuration/QoSOptions.cs.bak deleted file mode 100644 index 8e41e7d7..00000000 --- a/src/Ocelot/Configuration/QoSOptions.cs.bak +++ /dev/null @@ -1,35 +0,0 @@ -<<<<<<< HEAD -using Polly.Timeout; -using System; -======= -using System; -using Polly.Timeout; ->>>>>>> refs/remotes/origin/develop - -namespace Ocelot.Configuration -{ - public class QoSOptions - { - public QoSOptions( - int exceptionsAllowedBeforeBreaking, - int durationofBreak, - int timeoutValue, - TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) - { - ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); - TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue); - TimeoutStrategy = timeoutStrategy; - } - - - public int ExceptionsAllowedBeforeBreaking { get; private set; } - - public TimeSpan DurationOfBreak { get; private set; } - - public TimeSpan TimeoutValue { get; private set; } - - public TimeoutStrategy TimeoutStrategy { get; private set; } - - } -} diff --git a/src/Ocelot/Configuration/ReRoute.cs.bak b/src/Ocelot/Configuration/ReRoute.cs.bak deleted file mode 100644 index 4ef5b08a..00000000 --- a/src/Ocelot/Configuration/ReRoute.cs.bak +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Values; - -namespace Ocelot.Configuration -{ - public class ReRoute - { - public ReRoute(PathTemplate downstreamPathTemplate, - PathTemplate upstreamTemplate, - HttpMethod upstreamHttpMethod, - string upstreamTemplatePattern, - bool isAuthenticated, - AuthenticationOptions authenticationOptions, - List configurationHeaderExtractorProperties, - List claimsToClaims, - Dictionary routeClaimsRequirement, - bool isAuthorised, - List claimsToQueries, -<<<<<<< HEAD - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, - string downstreamScheme, string loadBalancer, string downstreamHost, - int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions) -======= - string requestIdKey, - bool isCached, - CacheOptions fileCacheOptions, - string downstreamScheme, - string loadBalancer, - string downstreamHost, - int downstreamPort, - string reRouteKey, - ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos, - QoSOptions qos) ->>>>>>> refs/remotes/origin/develop - { - ReRouteKey = reRouteKey; - ServiceProviderConfiguraion = serviceProviderConfiguraion; - LoadBalancer = loadBalancer; - DownstreamHost = downstreamHost; - DownstreamPort = downstreamPort; - DownstreamPathTemplate = downstreamPathTemplate; - UpstreamPathTemplate = upstreamTemplate; - UpstreamHttpMethod = upstreamHttpMethod; - UpstreamTemplatePattern = upstreamTemplatePattern; - IsAuthenticated = isAuthenticated; - AuthenticationOptions = authenticationOptions; - RouteClaimsRequirement = routeClaimsRequirement; - IsAuthorised = isAuthorised; - RequestIdKey = requestIdKey; - IsCached = isCached; - FileCacheOptions = fileCacheOptions; - ClaimsToQueries = claimsToQueries - ?? new List(); - ClaimsToClaims = claimsToClaims - ?? new List(); - ClaimsToHeaders = configurationHeaderExtractorProperties - ?? new List(); - DownstreamScheme = downstreamScheme; - IsQos = isQos; - QosOptions = qos; - EnableEndpointRateLimiting = enableRateLimit; - RateLimitOptions = ratelimitOptions; - } - - public string ReRouteKey {get;private set;} - public PathTemplate DownstreamPathTemplate { get; private set; } - public PathTemplate UpstreamPathTemplate { get; private set; } - public string UpstreamTemplatePattern { get; private set; } - public HttpMethod UpstreamHttpMethod { get; private set; } - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public AuthenticationOptions AuthenticationOptions { get; private set; } - public List ClaimsToQueries { get; private set; } - public List ClaimsToHeaders { get; private set; } - public List ClaimsToClaims { get; private set; } - public Dictionary RouteClaimsRequirement { get; private set; } - public string RequestIdKey { get; private set; } - public bool IsCached { get; private set; } - 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 string LoadBalancer {get;private set;} - public string DownstreamHost { get; private set; } - public int DownstreamPort { get; private set; } - public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; } - public bool EnableEndpointRateLimiting { get; private set; } - public RateLimitOptions RateLimitOptions { get; private set; } - } -} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 0a65abc2..aba4cdf9 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file From 0b4aee7b966ae32082e5904d395a9b462d94bac1 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Wed, 15 Feb 2017 20:12:47 +0000 Subject: [PATCH 08/15] Add syntax highlighting to readme.md --- README.md | 177 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index dfbdfd1e..0a918587 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,12 @@ The ReRoutes are the objects that tell Ocelot how to treat an upstream request. configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful if you don't want to manage lots of ReRoute specific settings. - { - "ReRoutes": [], - "GlobalConfiguration": {} - } - +```json + { + "ReRoutes": [], + "GlobalConfiguration": {} + } +``` More information on how to use these options is below.. ## Startup @@ -73,7 +74,8 @@ More information on how to use these options is below.. An example startup using a json file for configuration can be seen below. Currently this is the only way to get configuration into Ocelot. - public class Startup +```csharp + public class Startup { public Startup(IHostingEnvironment env) { @@ -112,7 +114,7 @@ Currently this is the only way to get configuration into Ocelot. app.UseOcelot(); } } - +``` This is pretty much all you need to get going.......more to come! @@ -127,22 +129,26 @@ Ocelot always adds a trailing slash to an UpstreamPathTemplate. Ocelot's describes the routing of one request to another as a ReRoute. In order to get anything working in Ocelot you need to set up a ReRoute in the configuration. - { - "ReRoutes": [ - ] - } +```json + { + "ReRoutes": [ + ] + } +``` In order to set up a ReRoute you need to add one to the json array called ReRoutes like the following. - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost" "localhost" - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put" - } +```json + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "localhost" + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put" + } +``` The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. The UpstreamPathTemplate is the URL that Ocelot will use to identity which @@ -156,7 +162,9 @@ Upstream URL when the request comes in. At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive. In order to change this you can specify on a per ReRoute basis the following setting. - "ReRouteIsCaseSensitive": true +```json + "ReRouteIsCaseSensitive": true +``` This means that when Ocelot tries to match the incoming upstream url with an upstream template the evaluation will be case sensitive. This setting defaults to false so only set it if you want @@ -176,25 +184,28 @@ At the moment the only supported service discovery provider is Consul. The follo GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default will be used. - "ServiceDiscoveryProvider": - { - "Provider":"Consul", - "Host":"localhost", - "Port":8500 - } +```json + "ServiceDiscoveryProvider": { + "Provider":"Consul", + "Host":"localhost", + "Port":8500 + } +``` In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put", - "ServiceName": "product" - "LoadBalancer": "LeastConnection" - } +```json + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put", + "ServiceName": "product" + "LoadBalancer": "LeastConnection" + } +``` When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balancer requests across any available services. @@ -206,16 +217,18 @@ Ocelot currently supports the use of bearer tokens with Identity Server (more pr come if required). In order to identity a ReRoute as authenticated it needs the following configuration added. - "AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ScopeName": "api", - "AdditionalScopes": [ - "openid", - "offline_access" - ], - "ScopeSecret": "secret" - } +```json + "AuthenticationOptions": { + "Provider": "IdentityServer", + "ProviderRootUrl": "http://localhost:52888", + "ScopeName": "api", + "AdditionalScopes": [ + "openid", + "offline_access" + ], + "ScopeSecret": "secret" + } +``` In this example the Provider is specified as IdentityServer. This string is important because it is used to identity the authentication provider (as previously mentioned in @@ -234,9 +247,11 @@ is 401 unauthorised. Ocelot supports claims based authorisation which is run post authentication. This means if you have a route you want to authorise you can add the following to you ReRoute configuration. - "RouteClaimsRequirement": { - "UserType": "registered" - }, +```json + "RouteClaimsRequirement": { + "UserType": "registered" + }, +``` In this example when the authorisation middleware is called Ocelot will check to see if the user has the claim type UserType and if the value of that claim is registered. @@ -275,10 +290,12 @@ and add whatever was at the index requested to the transform. Below is an example configuration that will transforms claims to claims - "AddClaimsToRequest": { - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, +```json + "AddClaimsToRequest": { + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" + }, +``` This shows a transforms where Ocelot looks at the users sub claim and transforms it into UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". @@ -287,9 +304,11 @@ UserType and UserId claims. Assuming the sub looks like this "usertypevalue|user Below is an example configuration that will transforms claims to headers - "AddHeadersToRequest": { - "CustomerId": "Claims[sub] > value[1] > |" - }, +```json + "AddHeadersToRequest": { + "CustomerId": "Claims[sub] > value[1] > |" + }, +``` This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". @@ -298,9 +317,11 @@ CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". Below is an example configuration that will transforms claims to query string parameters - "AddQueriesToRequest": { - "LocationId": "Claims[LocationId] > value", - }, +```json + "AddQueriesToRequest": { + "LocationId": "Claims[LocationId] > value", + }, +``` This shows a transform where Ocelot looks at the users LocationId claim and add its as a query string parameter to be forwarded onto the downstream service. @@ -313,11 +334,13 @@ want to use a circuit breaker when making requests to a downstream service. This Add the following section to a ReRoute configuration. - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":5, - "TimeoutValue":5000 - } +```json + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking":3, + "DurationOfBreak":5, + "TimeoutValue":5000 + } +``` You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped. @@ -338,14 +361,18 @@ have an OcelotRequestId. In order to use the requestid feature in your ReRoute configuration add this setting - "RequestIdKey": "OcRequestId" +```json + "RequestIdKey": "OcRequestId" +``` In this example OcRequestId is the request header that contains the clients request id. There is also a setting in the GlobalConfiguration section which will override whatever has been set at ReRoute level for the request id. The setting is as fllows. - "RequestIdKey": "OcRequestId", +```json + "RequestIdKey": "OcRequestId", +``` It behaves in exactly the same way as the ReRoute level RequestIdKey settings. @@ -364,7 +391,9 @@ and setting a TTL in seconds to expire the cache. More to come! In orde to use caching on a route in your ReRoute configuration add this setting. - "FileCacheOptions": { "TtlSeconds": 15 } +```json + "FileCacheOptions": { "TtlSeconds": 15 } +``` In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. @@ -376,15 +405,17 @@ pipeline and you are using any of the following. Remove them and try again! When setting up Ocelot in your Startup.cs you can provide some additonal middleware and override middleware. This is done as follos. - var configuration = new OcelotMiddlewareConfiguration - { - PreErrorResponderMiddleware = async (ctx, next) => - { - await next.Invoke(); - } - }; +```csharp + var configuration = new OcelotMiddlewareConfiguration + { + PreErrorResponderMiddleware = async (ctx, next) => + { + await next.Invoke(); + } + }; - app.UseOcelot(configuration); + app.UseOcelot(configuration); +``` In the example above the provided function will run before the first piece of Ocelot middleware. This allows a user to supply any behaviours they want before and after the Ocelot pipeline has run. From 45341df237c06481405417064cb6fd585b7298c9 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Wed, 15 Feb 2017 20:18:15 +0000 Subject: [PATCH 09/15] Remove extra level of indentation. Fixed a typo in one of tje json examples --- README.md | 200 +++++++++++++++++++++++++++--------------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 0a918587..6fcf10f4 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,10 @@ configuration is a bit hacky and allows overrides of ReRoute specific settings. if you don't want to manage lots of ReRoute specific settings. ```json - { - "ReRoutes": [], - "GlobalConfiguration": {} - } +{ + "ReRoutes": [], + "GlobalConfiguration": {} +} ``` More information on how to use these options is below.. @@ -75,45 +75,45 @@ An example startup using a json file for configuration can be seen below. Currently this is the only way to get configuration into Ocelot. ```csharp - public class Startup +public class Startup +{ + public Startup(IHostingEnvironment env) { - public Startup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - services.AddOcelotOutputCaching(settings); - services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - loggerFactory.AddConsole(Configuration.GetSection("Logging")); - - app.UseOcelot(); - } + Configuration = builder.Build(); } + + public IConfigurationRoot Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + services.AddOcelotOutputCaching(settings); + services.AddOcelotFileConfiguration(Configuration); + services.AddOcelot(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + + app.UseOcelot(); + } +} ``` This is pretty much all you need to get going.......more to come! @@ -140,14 +140,14 @@ In order to set up a ReRoute you need to add one to the json array called ReRout the following. ```json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost" "localhost" - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put" - } +{ + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "localhost" + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put" +} ``` The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. @@ -163,7 +163,7 @@ At the moment without any configuration Ocelot will default to all ReRoutes bein In order to change this you can specify on a per ReRoute basis the following setting. ```json - "ReRouteIsCaseSensitive": true +"ReRouteIsCaseSensitive": true ``` This means that when Ocelot tries to match the incoming upstream url with an upstream template the @@ -185,11 +185,11 @@ GlobalConfiguration. The Provider is required and if you do not specify a host a will be used. ```json - "ServiceDiscoveryProvider": { - "Provider":"Consul", - "Host":"localhost", - "Port":8500 - } +"ServiceDiscoveryProvider": { + "Provider":"Consul", + "Host":"localhost", + "Port":8500 +} ``` In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the @@ -197,14 +197,14 @@ ServiceName and load balancer you wish to use when making requests downstream. A and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. ```json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put", - "ServiceName": "product" - "LoadBalancer": "LeastConnection" - } +{ + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put", + "ServiceName": "product", + "LoadBalancer": "LeastConnection" +} ``` When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balancer @@ -218,16 +218,16 @@ come if required). In order to identity a ReRoute as authenticated it needs the configuration added. ```json - "AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ScopeName": "api", - "AdditionalScopes": [ - "openid", - "offline_access" - ], - "ScopeSecret": "secret" - } +"AuthenticationOptions": { + "Provider": "IdentityServer", + "ProviderRootUrl": "http://localhost:52888", + "ScopeName": "api", + "AdditionalScopes": [ + "openid", + "offline_access" + ], + "ScopeSecret": "secret" +} ``` In this example the Provider is specified as IdentityServer. This string is important @@ -248,9 +248,9 @@ Ocelot supports claims based authorisation which is run post authentication. Thi you have a route you want to authorise you can add the following to you ReRoute configuration. ```json - "RouteClaimsRequirement": { - "UserType": "registered" - }, +"RouteClaimsRequirement": { + "UserType": "registered" +}, ``` In this example when the authorisation middleware is called Ocelot will check to see @@ -291,10 +291,10 @@ and add whatever was at the index requested to the transform. Below is an example configuration that will transforms claims to claims ```json - "AddClaimsToRequest": { - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, + "AddClaimsToRequest": { + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" +}, ``` This shows a transforms where Ocelot looks at the users sub claim and transforms it into @@ -305,9 +305,9 @@ UserType and UserId claims. Assuming the sub looks like this "usertypevalue|user Below is an example configuration that will transforms claims to headers ```json - "AddHeadersToRequest": { - "CustomerId": "Claims[sub] > value[1] > |" - }, +"AddHeadersToRequest": { + "CustomerId": "Claims[sub] > value[1] > |" +}, ``` This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a @@ -318,9 +318,9 @@ CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". Below is an example configuration that will transforms claims to query string parameters ```json - "AddQueriesToRequest": { - "LocationId": "Claims[LocationId] > value", - }, +"AddQueriesToRequest": { + "LocationId": "Claims[LocationId] > value", +}, ``` This shows a transform where Ocelot looks at the users LocationId claim and add its as @@ -335,11 +335,11 @@ want to use a circuit breaker when making requests to a downstream service. This Add the following section to a ReRoute configuration. ```json - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":5, - "TimeoutValue":5000 - } +"QoSOptions": { + "ExceptionsAllowedBeforeBreaking":3, + "DurationOfBreak":5, + "TimeoutValue":5000 +} ``` You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be @@ -362,7 +362,7 @@ have an OcelotRequestId. In order to use the requestid feature in your ReRoute configuration add this setting ```json - "RequestIdKey": "OcRequestId" +"RequestIdKey": "OcRequestId" ``` In this example OcRequestId is the request header that contains the clients request id. @@ -371,7 +371,7 @@ There is also a setting in the GlobalConfiguration section which will override w set at ReRoute level for the request id. The setting is as fllows. ```json - "RequestIdKey": "OcRequestId", +"RequestIdKey": "OcRequestId", ``` It behaves in exactly the same way as the ReRoute level RequestIdKey settings. @@ -392,7 +392,7 @@ and setting a TTL in seconds to expire the cache. More to come! In orde to use caching on a route in your ReRoute configuration add this setting. ```json - "FileCacheOptions": { "TtlSeconds": 15 } +"FileCacheOptions": { "TtlSeconds": 15 } ``` In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. @@ -406,15 +406,15 @@ When setting up Ocelot in your Startup.cs you can provide some additonal middlew and override middleware. This is done as follos. ```csharp - var configuration = new OcelotMiddlewareConfiguration +var configuration = new OcelotMiddlewareConfiguration +{ + PreErrorResponderMiddleware = async (ctx, next) => { - PreErrorResponderMiddleware = async (ctx, next) => - { - await next.Invoke(); - } - }; + await next.Invoke(); + } +}; - app.UseOcelot(configuration); +app.UseOcelot(configuration); ``` In the example above the provided function will run before the first piece of Ocelot middleware. From 4ad8accbc0f0b2e5b710d1f6897571b27aa23b6b Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Wed, 15 Feb 2017 20:30:35 +0000 Subject: [PATCH 10/15] grr! I always miss something... --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6fcf10f4..57faf26f 100644 --- a/README.md +++ b/README.md @@ -130,10 +130,10 @@ Ocelot's describes the routing of one request to another as a ReRoute. In order anything working in Ocelot you need to set up a ReRoute in the configuration. ```json - { - "ReRoutes": [ - ] - } +{ + "ReRoutes": [ + ] +} ``` In order to set up a ReRoute you need to add one to the json array called ReRoutes like From b98310c80d6a8d3cc78b8fe852279e323350989d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 17 Feb 2017 18:24:32 +0000 Subject: [PATCH 11/15] working ssl example --- test/Ocelot.AcceptanceTests/project.json | 1 + test/Ocelot.ManualTest/configuration.json | 44 +++++++++++------------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 2dd3094e..41594e1f 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -29,6 +29,7 @@ "IdentityServer4": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel.Https": "1.1.0", "Microsoft.NETCore.App": "1.1.0", "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 9b5fcb7e..e8bc8928 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -5,7 +5,7 @@ "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 52876, - "UpstreamTemplate": "/identityserverexample", + "UpstreamPathTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -47,10 +47,10 @@ }, { "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", + "DownstreamScheme": "https", "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "DownstreamPort": 443, + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -63,7 +63,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -76,7 +76,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}/comments", + "UpstreamPathTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -89,7 +89,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/comments", + "UpstreamPathTemplate": "/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -102,7 +102,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -115,7 +115,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -128,7 +128,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -141,7 +141,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -154,7 +154,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -168,7 +168,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, @@ -177,7 +177,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -190,7 +190,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -204,7 +204,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -218,7 +218,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -232,7 +232,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -246,7 +246,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -260,7 +260,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -274,7 +274,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -288,7 +288,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/", + "UpstreamPathTemplate": "/posts/", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, From ed3a629827e561b1b064641088a272787c5490f8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 17 Feb 2017 20:17:23 +0000 Subject: [PATCH 12/15] updated nuget info --- README.md | 3 --- src/Ocelot/project.json | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57faf26f..3d390a3d 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,6 @@ and retrived as the requests goes back up the Ocelot pipeline. There is a piece that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. -This is not ready for production yet as uses a lot of rc and beta .net core packages. -Hopefully by the start of 2017 it will be in use. - ## Contributing Pull requests, issues and commentary welcome! No special process just create a request and get in diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 4e098803..0ab54df2 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,5 +1,13 @@ { "version": "0.0.0-dev", + "title": "Ocelot", + "summary": "API Gateway created using .NET core.", + "projectUrl": "https://github.com/TomPallister/Ocelot", + "description": "This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.", + "tags": [ + "API Gateway", + ".NET core" + ], "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", From 3c9ad87b73924fa7d53a8649d787b0415bd19190 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 19 Feb 2017 15:03:11 +0000 Subject: [PATCH 13/15] Fix cake on Mac. Also added compile-time support for OSX.10.12-x64. The acceptance tests still fail on this version of OS X, however, because of the funky stuff going on in TestConfiguration.cs. Fixing this is outside the scope of this issue. --- build.cake | 32 ++++++++++++++---------- test/Ocelot.AcceptanceTests/project.json | 7 +++--- test/Ocelot.Benchmarks/project.json | 5 ++-- test/Ocelot.ManualTest/project.json | 5 ++-- test/Ocelot.UnitTests/project.json | 5 ++-- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/build.cake b/build.cake index 579f4e82..9a12e73a 100644 --- a/build.cake +++ b/build.cake @@ -80,7 +80,7 @@ Task("Version") if (AppVeyor.IsRunningOnAppVeyor) { Information("Persisting version number..."); - PersistVersion(nugetVersion); + PersistVersion(committedVersion, nugetVersion); buildVersion = nugetVersion; } else @@ -158,7 +158,7 @@ Task("CreatePackages") { EnsureDirectoryExists(packagesDir); - GenerateReleaseNotes(); + GenerateReleaseNotes(releaseNotesFile); var settings = new DotNetCorePackSettings { @@ -189,7 +189,7 @@ Task("ReleasePackagesToUnstableFeed") .IsDependentOn("CreatePackages") .Does(() => { - PublishPackages(nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); + PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); }); Task("EnsureStableReleaseRequirements") @@ -241,7 +241,7 @@ Task("ReleasePackagesToStableFeed") .IsDependentOn("DownloadGitHubReleaseArtifacts") .Does(() => { - PublishPackages(nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); }); Task("Release") @@ -262,9 +262,9 @@ private string GetNuGetVersionForCommit() } /// Updates project version in all of our projects -private void PersistVersion(string version) +private void PersistVersion(string committedVersion, string newVersion) { - Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, newVersion)); var projectJsonFiles = GetFiles("./**/project.json"); @@ -275,24 +275,30 @@ private void PersistVersion(string version) Information(string.Format("Updating {0}...", file)); var updatedProjectJson = System.IO.File.ReadAllText(file) - .Replace(committedVersion, version); + .Replace(committedVersion, newVersion); System.IO.File.WriteAllText(file, updatedProjectJson); } } /// generates release notes based on issues closed in GitHub since the last release -private void GenerateReleaseNotes() +private void GenerateReleaseNotes(string file) { - Information("Generating release notes at " + releaseNotesFile); + if (!IsRunningOnWindows()) + { + Warning("We can't generate release notes as we're not running on Windows."); + return; + } + + Information("Generating release notes at " + file); var releaseNotesExitCode = StartProcess( @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", - new ProcessSettings { Arguments = ". /o " + releaseNotesFile }); + new ProcessSettings { Arguments = ". /o " + file }); - if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file))) { - System.IO.File.WriteAllText(releaseNotesFile, "No issues closed since last release"); + System.IO.File.WriteAllText(file, "No issues closed since last release"); } if (releaseNotesExitCode != 0) @@ -302,7 +308,7 @@ private void GenerateReleaseNotes() } /// Publishes code and symbols packages to nuget feed, based on contents of artifacts file -private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +private void PublishPackages(string packagesDir, string artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) { var artifacts = System.IO.File .ReadAllLines(artifactsFile) diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 41594e1f..7739992a 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -38,9 +38,10 @@ "xunit": "2.2.0-rc1-build3507" }, "runtimes": { - "win10-x64": {}, - "osx.10.11-x64": {}, - "win7-x64": {} + "osx.10.11-x64":{}, + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 061a2223..615b9d0e 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -9,9 +9,10 @@ "BenchmarkDotNet": "0.10.2" }, "runtimes": { - "win10-x64": {}, "osx.10.11-x64":{}, - "win7-x64": {} + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index cf67f9bd..433b1dc0 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -19,9 +19,10 @@ "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "runtimes": { - "win10-x64": {}, "osx.10.11-x64":{}, - "win7-x64": {} + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 2f42c283..30ad3684 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -27,9 +27,10 @@ "xunit": "2.2.0-rc1-build3507" }, "runtimes": { - "win10-x64": {}, "osx.10.11-x64":{}, - "win7-x64": {} + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { From eb5dfc697538f8956e1dd3f04edf45fba3c686f6 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 15:43:58 +0000 Subject: [PATCH 14/15] removed test configuration nonsense --- .../TestConfiguration.cs | 36 +++---------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..3265955d 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -1,36 +1,10 @@ -namespace Ocelot.AcceptanceTests -{ - using System.Runtime.InteropServices; +using System; +using System.IO; +namespace Ocelot.AcceptanceTests +{ public static class TestConfiguration { - public static double Version => 1.1; - public static string ConfigurationPath => GetConfigurationPath(); - - public static string GetConfigurationPath() - { - var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); - - if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) - { - return FormatConfigurationPath("osx.10.11", osArchitecture); - } - - if(RuntimeInformation.OSDescription.ToLower().Contains("microsoft windows 10")) - { - return FormatConfigurationPath("win10", osArchitecture); - } - - return FormatConfigurationPath("win7", osArchitecture); - } - - private static string FormatConfigurationPath(string oSDescription, string osArchitecture) - { - var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; - - return configPath; - } + public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json"); } } From f1a403ae29bbc5b5d2d096e5a67bd07c5d8c66f1 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 19 Feb 2017 20:56:05 +0000 Subject: [PATCH 15/15] Fix package publishing --- build.cake | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build.cake b/build.cake index 9a12e73a..94af4431 100644 --- a/build.cake +++ b/build.cake @@ -282,7 +282,7 @@ private void PersistVersion(string committedVersion, string newVersion) } /// generates release notes based on issues closed in GitHub since the last release -private void GenerateReleaseNotes(string file) +private void GenerateReleaseNotes(ConvertableFilePath file) { if (!IsRunningOnWindows()) { @@ -308,7 +308,7 @@ private void GenerateReleaseNotes(string file) } /// Publishes code and symbols packages to nuget feed, based on contents of artifacts file -private void PublishPackages(string packagesDir, string artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) { var artifacts = System.IO.File .ReadAllLines(artifactsFile) @@ -317,8 +317,7 @@ private void PublishPackages(string packagesDir, string artifactsFile, string fe var codePackage = packagesDir + File(artifacts["nuget"]); - Information("Pushing package"); - + Information("Pushing package " + codePackage); NuGetPush( codePackage, new NuGetPushSettings {