mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 14:02:49 +08:00
Merge pull request #105 from geffzhang/develop
I've applied Ocelot to the product,bug fixed
This commit is contained in:
commit
6648263ef8
@ -28,6 +28,12 @@ namespace Ocelot.Configuration.Validator
|
||||
|
||||
result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration);
|
||||
|
||||
if (result.IsError)
|
||||
{
|
||||
return new OkResponse<ConfigurationValidationResult>(result);
|
||||
}
|
||||
result = CheckForReRoutesRateLimitOptions(configuration);
|
||||
|
||||
if (result.IsError)
|
||||
{
|
||||
return new OkResponse<ConfigurationValidationResult>(result);
|
||||
@ -111,5 +117,35 @@ namespace Ocelot.Configuration.Validator
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
RequestTimedOutError,
|
||||
UnableToFindQoSProviderError,
|
||||
UnableToSetConfigInConsulError,
|
||||
UnmappableRequestError
|
||||
UnmappableRequestError,
|
||||
RateLimitOptionsError
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
switch (reRoute.LoadBalancer)
|
||||
{
|
||||
case "RoundRobin":
|
||||
return new RoundRobinLoadBalancer(await serviceProvider.Get());
|
||||
return new RoundRobinLoadBalancer(async () => await serviceProvider.Get());
|
||||
case "LeastConnection":
|
||||
return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName);
|
||||
default:
|
||||
|
@ -2,27 +2,31 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.Values;
|
||||
using System;
|
||||
|
||||
namespace Ocelot.LoadBalancer.LoadBalancers
|
||||
{
|
||||
public class RoundRobinLoadBalancer : ILoadBalancer
|
||||
{
|
||||
private readonly List<Service> _services;
|
||||
private readonly Func<Task<List<Service>>> _services;
|
||||
|
||||
private int _last;
|
||||
|
||||
public RoundRobinLoadBalancer(List<Service> services)
|
||||
public RoundRobinLoadBalancer(Func<Task<List<Service>>> services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
|
||||
public async Task<Response<HostAndPort>> Lease()
|
||||
{
|
||||
if (_last >= _services.Count)
|
||||
var services = await _services.Invoke();
|
||||
if (_last >= services.Count)
|
||||
{
|
||||
_last = 0;
|
||||
}
|
||||
|
||||
var next = await Task.FromResult(_services[_last]);
|
||||
var next = await Task.FromResult(services[_last]);
|
||||
_last++;
|
||||
return new OkResponse<HostAndPort>(next.HostAndPort);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.RateLimit
|
||||
{
|
||||
|
||||
public class ClientRateLimitProcessor
|
||||
{
|
||||
private readonly IRateLimitCounterHandler _counterHandler;
|
||||
@ -23,7 +24,8 @@ namespace Ocelot.RateLimit
|
||||
return _core.ProcessRequest(requestIdentity, option);
|
||||
}
|
||||
|
||||
public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
|
||||
|
||||
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
|
||||
{
|
||||
return _core.RetryAfterFrom(timestamp, rule);
|
||||
}
|
||||
@ -33,5 +35,11 @@ namespace Ocelot.RateLimit
|
||||
return _core.GetRateLimitHeaders(context, requestIdentity, option);
|
||||
}
|
||||
|
||||
public TimeSpan ConvertToTimeSpan(string timeSpan)
|
||||
{
|
||||
return _core.ConvertToTimeSpan(timeSpan);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,11 +75,14 @@ namespace Ocelot.RateLimit.Middleware
|
||||
// log blocked request
|
||||
LogBlockedRequest(context, identity, counter, rule);
|
||||
|
||||
var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
// break execution
|
||||
await ReturnQuotaExceededResponse(context, options, retryAfter);
|
||||
await ReturnQuotaExceededResponse(context, options, retrystring);
|
||||
_logger.TraceMiddlewareCompleted();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
//set X-Rate-Limit headers for the longest period
|
||||
if (!options.DisableRateLimitHeaders)
|
||||
@ -111,7 +114,11 @@ namespace Ocelot.RateLimit.Middleware
|
||||
|
||||
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)
|
||||
@ -144,6 +151,6 @@ namespace Ocelot.RateLimit.Middleware
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -34,23 +34,45 @@ namespace Ocelot.RateLimit
|
||||
if (entry.HasValue)
|
||||
{
|
||||
// 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
|
||||
var totalRequests = entry.Value.TotalRequests + 1;
|
||||
|
||||
// deep copy
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var rule = option.RateLimitRule;
|
||||
@ -92,12 +114,12 @@ namespace Ocelot.RateLimit
|
||||
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 retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
|
||||
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
|
||||
return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
return retryAfter;
|
||||
}
|
||||
|
||||
public TimeSpan ConvertToTimeSpan(string timeSpan)
|
||||
|
@ -22,7 +22,9 @@ namespace Ocelot.Requester
|
||||
|
||||
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
|
||||
{
|
||||
IHttpClient httpClient = GetHttpClient(request);
|
||||
var cacheKey = GetCacheKey(request);
|
||||
|
||||
IHttpClient httpClient = GetHttpClient(cacheKey);
|
||||
try
|
||||
{
|
||||
var response = await httpClient.SendAsync(request.HttpRequestMessage);
|
||||
@ -42,32 +44,28 @@ namespace Ocelot.Requester
|
||||
{
|
||||
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 cacheKey = GetCacheKey(request, builder);
|
||||
|
||||
var httpClient = _cacheHandlers.Get(cacheKey);
|
||||
if (httpClient == null)
|
||||
{
|
||||
httpClient = builder.Create();
|
||||
}
|
||||
_cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(6));
|
||||
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}";
|
||||
|
||||
if (request.IsQos)
|
||||
{
|
||||
builder.WithQos(request.QosProvider, _logger);
|
||||
baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
|
||||
}
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using Ocelot.Values;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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])
|
||||
};
|
||||
|
||||
_roundRobin = new RoundRobinLoadBalancer(_services);
|
||||
_roundRobin = new RoundRobinLoadBalancer(() => Task.FromResult(_services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
Loading…
x
Reference in New Issue
Block a user