Merge pull request #105 from geffzhang/develop

I've applied Ocelot to the product,bug fixed
This commit is contained in:
Tom Pallister 2017-06-10 15:19:47 +01:00 committed by GitHub
commit 6648263ef8
10 changed files with 136 additions and 44 deletions

View File

@ -28,6 +28,12 @@ namespace Ocelot.Configuration.Validator
result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration); result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration);
if (result.IsError)
{
return new OkResponse<ConfigurationValidationResult>(result);
}
result = CheckForReRoutesRateLimitOptions(configuration);
if (result.IsError) if (result.IsError)
{ {
return new OkResponse<ConfigurationValidationResult>(result); return new OkResponse<ConfigurationValidationResult>(result);
@ -111,5 +117,35 @@ namespace Ocelot.Configuration.Validator
return new ConfigurationValidationResult(true, errors); return new ConfigurationValidationResult(true, errors);
} }
private ConfigurationValidationResult CheckForReRoutesRateLimitOptions(FileConfiguration configuration)
{
var errors = new List<Error>();
foreach (var reRoute in configuration.ReRoutes)
{
if (reRoute.RateLimitOptions.EnableRateLimiting)
{
if (!IsValidPeriod(reRoute))
{
errors.Add(new RateLimitOptionsValidationError($"{reRoute.RateLimitOptions.Period} not contains scheme"));
}
}
}
if (errors.Any())
{
return new ConfigurationValidationResult(true, errors);
}
return new ConfigurationValidationResult(false, errors);
}
private static bool IsValidPeriod(FileReRoute reRoute)
{
string period = reRoute.RateLimitOptions.Period;
return period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d");
}
} }
} }

View File

@ -0,0 +1,15 @@
using Ocelot.Errors;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Configuration.Validator
{
public class RateLimitOptionsValidationError : Error
{
public RateLimitOptionsValidationError(string message)
: base(message, OcelotErrorCode.RateLimitOptionsError)
{
}
}
}

View File

@ -29,6 +29,7 @@
RequestTimedOutError, RequestTimedOutError,
UnableToFindQoSProviderError, UnableToFindQoSProviderError,
UnableToSetConfigInConsulError, UnableToSetConfigInConsulError,
UnmappableRequestError UnmappableRequestError,
RateLimitOptionsError
} }
} }

View File

@ -19,7 +19,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
switch (reRoute.LoadBalancer) switch (reRoute.LoadBalancer)
{ {
case "RoundRobin": case "RoundRobin":
return new RoundRobinLoadBalancer(await serviceProvider.Get()); return new RoundRobinLoadBalancer(async () => await serviceProvider.Get());
case "LeastConnection": case "LeastConnection":
return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName); return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName);
default: default:

View File

@ -2,27 +2,31 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values; using Ocelot.Values;
using System;
namespace Ocelot.LoadBalancer.LoadBalancers namespace Ocelot.LoadBalancer.LoadBalancers
{ {
public class RoundRobinLoadBalancer : ILoadBalancer public class RoundRobinLoadBalancer : ILoadBalancer
{ {
private readonly List<Service> _services; private readonly Func<Task<List<Service>>> _services;
private int _last; private int _last;
public RoundRobinLoadBalancer(List<Service> services) public RoundRobinLoadBalancer(Func<Task<List<Service>>> services)
{ {
_services = services; _services = services;
} }
public async Task<Response<HostAndPort>> Lease() public async Task<Response<HostAndPort>> Lease()
{ {
if (_last >= _services.Count) var services = await _services.Invoke();
if (_last >= services.Count)
{ {
_last = 0; _last = 0;
} }
var next = await Task.FromResult(_services[_last]); var next = await Task.FromResult(services[_last]);
_last++; _last++;
return new OkResponse<HostAndPort>(next.HostAndPort); return new OkResponse<HostAndPort>(next.HostAndPort);
} }

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
namespace Ocelot.RateLimit namespace Ocelot.RateLimit
{ {
public class ClientRateLimitProcessor public class ClientRateLimitProcessor
{ {
private readonly IRateLimitCounterHandler _counterHandler; private readonly IRateLimitCounterHandler _counterHandler;
@ -23,7 +24,8 @@ namespace Ocelot.RateLimit
return _core.ProcessRequest(requestIdentity, option); return _core.ProcessRequest(requestIdentity, option);
} }
public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
{ {
return _core.RetryAfterFrom(timestamp, rule); return _core.RetryAfterFrom(timestamp, rule);
} }
@ -33,5 +35,11 @@ namespace Ocelot.RateLimit
return _core.GetRateLimitHeaders(context, requestIdentity, option); return _core.GetRateLimitHeaders(context, requestIdentity, option);
} }
public TimeSpan ConvertToTimeSpan(string timeSpan)
{
return _core.ConvertToTimeSpan(timeSpan);
}
} }
} }

View File

@ -40,7 +40,7 @@ namespace Ocelot.RateLimit.Middleware
_logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}"); _logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}");
_logger.TraceInvokeNext(); _logger.TraceInvokeNext();
await _next.Invoke(context); await _next.Invoke(context);
_logger.TraceInvokeNextCompleted(); _logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted(); _logger.TraceMiddlewareCompleted();
return; return;
@ -54,7 +54,7 @@ namespace Ocelot.RateLimit.Middleware
_logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting"); _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting");
_logger.TraceInvokeNext(); _logger.TraceInvokeNext();
await _next.Invoke(context); await _next.Invoke(context);
_logger.TraceInvokeNextCompleted(); _logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted(); _logger.TraceMiddlewareCompleted();
return; return;
@ -75,21 +75,24 @@ namespace Ocelot.RateLimit.Middleware
// log blocked request // log blocked request
LogBlockedRequest(context, identity, counter, rule); LogBlockedRequest(context, identity, counter, rule);
var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
// break execution // break execution
await ReturnQuotaExceededResponse(context, options, retryAfter); await ReturnQuotaExceededResponse(context, options, retrystring);
_logger.TraceMiddlewareCompleted(); _logger.TraceMiddlewareCompleted();
return; return;
} }
} }
//set X-Rate-Limit headers for the longest period //set X-Rate-Limit headers for the longest period
if (!options.DisableRateLimitHeaders) if (!options.DisableRateLimitHeaders)
{ {
var headers = _processor.GetRateLimitHeaders( context,identity, options); var headers = _processor.GetRateLimitHeaders(context, identity, options);
context.Response.OnStarting(SetRateLimitHeaders, state: headers); context.Response.OnStarting(SetRateLimitHeaders, state: headers);
} }
_logger.TraceInvokeNext(); _logger.TraceInvokeNext();
await _next.Invoke(context); await _next.Invoke(context);
_logger.TraceInvokeNextCompleted(); _logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted(); _logger.TraceMiddlewareCompleted();
} }
@ -107,11 +110,15 @@ namespace Ocelot.RateLimit.Middleware
httpContext.Request.Path.ToString().ToLowerInvariant(), httpContext.Request.Path.ToString().ToLowerInvariant(),
httpContext.Request.Method.ToLowerInvariant() httpContext.Request.Method.ToLowerInvariant()
); );
} }
public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option)
{ {
return option.ClientWhitelist.Contains(requestIdentity.ClientId); if (option.ClientWhitelist.Contains(requestIdentity.ClientId))
{
return true;
}
return false;
} }
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule)
@ -144,6 +151,6 @@ namespace Ocelot.RateLimit.Middleware
} }
} }
} }

View File

@ -34,23 +34,45 @@ namespace Ocelot.RateLimit
if (entry.HasValue) if (entry.HasValue)
{ {
// entry has not expired // entry has not expired
if (entry.Value.Timestamp + TimeSpan.FromSeconds(rule.PeriodTimespan) >= DateTime.UtcNow) if (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period) >= DateTime.UtcNow)
{ {
// increment request count // increment request count
var totalRequests = entry.Value.TotalRequests + 1; var totalRequests = entry.Value.TotalRequests + 1;
// deep copy // deep copy
counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests); counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests);
} }
} }
// stores: id (string) - timestamp (datetime) - total_requests (long)
_counterHandler.Set(counterId, counter, TimeSpan.FromSeconds(rule.PeriodTimespan));
} }
if (counter.TotalRequests > rule.Limit)
{
var retryAfter = RetryAfterFrom(counter.Timestamp, rule);
if (retryAfter > 0)
{
var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan);
_counterHandler.Set(counterId, counter, expirationTime);
}
else
{
_counterHandler.Remove(counterId);
}
}
else
{
var expirationTime = ConvertToTimeSpan(rule.Period);
_counterHandler.Set(counterId, counter, expirationTime);
}
return counter; return counter;
} }
public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime)
{
var counterId = ComputeCounterKey(requestIdentity, option);
var rule = option.RateLimitRule;
// stores: id (string) - timestamp (datetime) - total_requests (long)
_counterHandler.Set(counterId, counter, expirationTime);
}
public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option)
{ {
var rule = option.RateLimitRule; var rule = option.RateLimitRule;
@ -63,7 +85,7 @@ namespace Ocelot.RateLimit
(rule.Limit - entry.Value.TotalRequests).ToString(), (rule.Limit - entry.Value.TotalRequests).ToString(),
(entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo) (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)
); );
} }
else else
{ {
headers = new RateLimitHeaders(context, headers = new RateLimitHeaders(context,
@ -74,17 +96,17 @@ namespace Ocelot.RateLimit
} }
return headers; return headers;
} }
public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option)
{ {
var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}";
var idBytes = Encoding.UTF8.GetBytes(key); var idBytes = Encoding.UTF8.GetBytes(key);
byte[] hashBytes; byte[] hashBytes;
using (var algorithm = SHA1.Create()) using (var algorithm = SHA1.Create())
{ {
hashBytes = algorithm.ComputeHash(idBytes); hashBytes = algorithm.ComputeHash(idBytes);
} }
@ -92,12 +114,12 @@ namespace Ocelot.RateLimit
return BitConverter.ToString(hashBytes).Replace("-", string.Empty); return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
} }
public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
{ {
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds); var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); return retryAfter;
} }
public TimeSpan ConvertToTimeSpan(string timeSpan) public TimeSpan ConvertToTimeSpan(string timeSpan)

View File

@ -22,7 +22,9 @@ namespace Ocelot.Requester
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
{ {
IHttpClient httpClient = GetHttpClient(request); var cacheKey = GetCacheKey(request);
IHttpClient httpClient = GetHttpClient(cacheKey);
try try
{ {
var response = await httpClient.SendAsync(request.HttpRequestMessage); var response = await httpClient.SendAsync(request.HttpRequestMessage);
@ -42,32 +44,28 @@ namespace Ocelot.Requester
{ {
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception)); return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
} }
finally
{
_cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24));
}
} }
private IHttpClient GetHttpClient(Request.Request request) private IHttpClient GetHttpClient(string cacheKey)
{ {
var builder = new HttpClientBuilder(); var builder = new HttpClientBuilder();
var cacheKey = GetCacheKey(request, builder);
var httpClient = _cacheHandlers.Get(cacheKey); var httpClient = _cacheHandlers.Get(cacheKey);
if (httpClient == null) if (httpClient == null)
{ {
httpClient = builder.Create(); httpClient = builder.Create();
} }
_cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(6));
return httpClient; return httpClient;
} }
private string GetCacheKey(Request.Request request, IHttpClientBuilder builder) private string GetCacheKey(Request.Request request)
{ {
string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}";
if (request.IsQos)
{
builder.WithQos(request.QosProvider, _logger);
baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
}
return baseUrl; return baseUrl;
} }
} }

View File

@ -6,6 +6,7 @@ using Ocelot.Values;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using System.Threading.Tasks;
namespace Ocelot.UnitTests.LoadBalancer namespace Ocelot.UnitTests.LoadBalancer
{ {
@ -24,7 +25,7 @@ namespace Ocelot.UnitTests.LoadBalancer
new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]) new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0])
}; };
_roundRobin = new RoundRobinLoadBalancer(_services); _roundRobin = new RoundRobinLoadBalancer(() => Task.FromResult(_services));
} }
[Fact] [Fact]