mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 16:48:15 +08:00
refactor code
This commit is contained in:
@ -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;
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace Ocelot.Configuration.File
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
|
||||
/// </summary>
|
||||
public int HttpStatusCode { get; private set; } = 429;
|
||||
public int HttpStatusCode { get; set; } = 429;
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace Ocelot.Configuration.File
|
||||
/// </summary>
|
||||
public string Period { get; set; }
|
||||
|
||||
public int PeriodTimespan { get; set; }
|
||||
public double PeriodTimespan { get; set; }
|
||||
/// <summary>
|
||||
/// Maximum number of requests that a client can make in a defined period
|
||||
/// </summary>
|
||||
|
@ -15,7 +15,7 @@ namespace Ocelot.Configuration
|
||||
{
|
||||
EnableRateLimiting = enbleRateLimiting;
|
||||
ClientIdHeader = clientIdHeader;
|
||||
ClientWhitelist = clientWhitelist;
|
||||
ClientWhitelist = clientWhitelist?? new List<string>();
|
||||
DisableRateLimitHeaders = disableRateLimitHeaders;
|
||||
QuotaExceededMessage = quotaExceededMessage;
|
||||
RateLimitCounterPrefix = rateLimitCounterPrefix;
|
||||
@ -62,15 +62,22 @@ namespace Ocelot.Configuration
|
||||
|
||||
public class RateLimitRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Rate limit period as in 1s, 1m, 1h
|
||||
/// </summary>
|
||||
public string Period { get; set; }
|
||||
public RateLimitRule(string period, TimeSpan periodTimespan, long limit)
|
||||
{
|
||||
Period = period;
|
||||
PeriodTimespan = periodTimespan;
|
||||
Limit = limit;
|
||||
}
|
||||
|
||||
public TimeSpan? PeriodTimespan { get; set; }
|
||||
/// <summary>
|
||||
/// Rate limit period as in 1s, 1m, 1h,1d
|
||||
/// </summary>
|
||||
public string Period { get; private set; }
|
||||
|
||||
public TimeSpan PeriodTimespan { get; private set; }
|
||||
/// <summary>
|
||||
/// Maximum number of requests that a client can make in a defined period
|
||||
/// </summary>
|
||||
public long Limit { get; set; }
|
||||
public long Limit { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ namespace Ocelot.DependencyInjection
|
||||
// could maybe use a scoped data repository
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddScoped<IRequestScopedDataRepository, HttpDataRepository>();
|
||||
|
||||
services.AddMemoryCache();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,14 @@ namespace Ocelot.RateLimit
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user