Skip to content

Commit df3d17d

Browse files
committed
limiter
1 parent 888ccc9 commit df3d17d

8 files changed

Lines changed: 247 additions & 187 deletions

File tree

ManagedCode.Orleans.RateLimiting.Client/Attributes/AuthorizedIpRateLimiterAttribute.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,4 @@ public AuthorizedIpRateLimiterAttribute(string configurationName)
1111
{
1212
ConfigurationName = configurationName;
1313
}
14-
}
15-
16-
17-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
18-
public class InRoleIpRateLimiterAttribute : Attribute, IRateLimiterAttribute
19-
{
20-
public string ConfigurationName { get; }
21-
public string Role { get; }
22-
23-
public InRoleIpRateLimiterAttribute(string configurationName, string role)
24-
{
25-
ConfigurationName = configurationName;
26-
Role = role;
27-
}
2814
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace ManagedCode.Orleans.RateLimiting.Client.Attributes;
4+
5+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
6+
public class InRoleIpRateLimiterAttribute : Attribute, IRateLimiterAttribute
7+
{
8+
public string ConfigurationName { get; }
9+
public string Role { get; }
10+
11+
public InRoleIpRateLimiterAttribute(string configurationName, string role)
12+
{
13+
ConfigurationName = configurationName;
14+
Role = role;
15+
}
16+
}

ManagedCode.Orleans.RateLimiting.Client/Extensions/ApplicationBuilderExtensions.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ namespace ManagedCode.Orleans.RateLimiting.Client.Extensions;
55

66
public static class ApplicationBuilderExtensions
77
{
8-
public static IApplicationBuilder AddOrleansRateLimiting(this IApplicationBuilder applicationBuilder)
8+
public static IApplicationBuilder UseOrleansIpRateLimiting(this IApplicationBuilder applicationBuilder)
99
{
10-
applicationBuilder.UseMiddleware<RateLimitingMiddleware>();
10+
applicationBuilder.UseMiddleware<OrleansIpRateLimitingMiddleware>();
11+
return applicationBuilder;
12+
}
13+
14+
public static IApplicationBuilder UseOrleansUserRateLimiting(this IApplicationBuilder applicationBuilder)
15+
{
16+
applicationBuilder.UseMiddleware<OrleansUserRateLimitingMiddleware>();
1117
return applicationBuilder;
1218
}
1319
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Net;
5+
using System.Reflection;
6+
using System.Threading.Tasks;
7+
using ManagedCode.Communication;
8+
using ManagedCode.Orleans.RateLimiting.Client.Attributes;
9+
using ManagedCode.Orleans.RateLimiting.Client.Extensions;
10+
using ManagedCode.Orleans.RateLimiting.Core.Extensions;
11+
using ManagedCode.Orleans.RateLimiting.Core.Models;
12+
using ManagedCode.Orleans.RateLimiting.Core.Models.Holders;
13+
using Microsoft.AspNetCore.Authorization;
14+
using Microsoft.AspNetCore.Http;
15+
using Microsoft.AspNetCore.Http.Extensions;
16+
using Microsoft.AspNetCore.Mvc.Controllers;
17+
using Microsoft.Extensions.DependencyInjection;
18+
using Microsoft.Extensions.Logging;
19+
using Orleans;
20+
21+
namespace ManagedCode.Orleans.RateLimiting.Client.Middlewares;
22+
23+
public abstract class OrleansBaseRateLimitingMiddleware
24+
{
25+
private readonly ILogger _logger;
26+
private readonly RequestDelegate _next;
27+
private readonly IClusterClient _client;
28+
private readonly IServiceProvider _services;
29+
30+
protected OrleansBaseRateLimitingMiddleware(ILogger logger, RequestDelegate next, IClusterClient client, IServiceProvider services)
31+
{
32+
_logger = logger;
33+
_next = next;
34+
_client = client;
35+
_services = services;
36+
}
37+
38+
protected abstract void AddLimiters(HttpContext httpContext, GroupLimiterHolder holder);
39+
public async Task Invoke(HttpContext httpContext)
40+
{
41+
await using var holder = new GroupLimiterHolder();
42+
43+
AddLimiters(httpContext, holder);
44+
45+
// throw too many requests if any of the limiters is null code 429
46+
var error = await holder.AcquireAsync();
47+
if (error is null)
48+
{
49+
await _next(httpContext);
50+
}
51+
else
52+
{
53+
httpContext.Response.Clear();
54+
httpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
55+
await httpContext.Response.WriteAsJsonAsync(Result.Fail(HttpStatusCode.TooManyRequests,error.ToException()));
56+
}
57+
}
58+
59+
protected (T attribute, string? postfix)? TryGetAttribute<T>(HttpContext httpContext) where T : Attribute, IRateLimiterAttribute
60+
{
61+
var endpoint = httpContext.GetEndpoint();
62+
63+
if(endpoint is null)
64+
return null;
65+
66+
// first try to get attribute from endpoint,
67+
var attribute = endpoint.Metadata.GetMetadata<T>();
68+
string postfix = endpoint.ToString()!;
69+
70+
if (attribute is null)
71+
{
72+
// then try to get attribute from controller
73+
var controllerType = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>()?.ControllerTypeInfo;
74+
75+
if (controllerType != null)
76+
{
77+
attribute = controllerType.GetCustomAttribute<T>(inherit: true);
78+
postfix = controllerType.ToString();
79+
}
80+
}
81+
82+
if (attribute is null)
83+
return null;
84+
85+
return (attribute, postfix);
86+
}
87+
88+
protected ILimiterHolder? TryGetLimiterHolder(HttpContext httpContext, string key, string configurationName)
89+
{
90+
var limiter = _client.GetRateLimiterByConfig(key, configurationName, _services.GetService<IEnumerable<RateLimiterConfig>>());
91+
92+
if(limiter is null)
93+
_logger.LogError($"Configuration {configurationName} not found for RateLimiter");
94+
95+
return limiter;
96+
}
97+
98+
protected string CreateKey(params string[] parts)
99+
{
100+
return string.Join(":", parts);
101+
}
102+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using ManagedCode.Orleans.RateLimiting.Client.Attributes;
3+
using ManagedCode.Orleans.RateLimiting.Client.Extensions;
4+
using ManagedCode.Orleans.RateLimiting.Core.Models.Holders;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Extensions.Logging;
7+
using Orleans;
8+
9+
namespace ManagedCode.Orleans.RateLimiting.Client.Middlewares;
10+
11+
public class OrleansIpRateLimitingMiddleware : OrleansBaseRateLimitingMiddleware
12+
{
13+
14+
public OrleansIpRateLimitingMiddleware(ILogger<OrleansIpRateLimitingMiddleware> logger, RequestDelegate next,
15+
IClusterClient client, IServiceProvider services) : base(logger, next, client, services)
16+
{
17+
18+
}
19+
20+
21+
protected override void AddLimiters(HttpContext httpContext, GroupLimiterHolder holder)
22+
{
23+
AddIpRateLimiter(httpContext, holder);
24+
}
25+
26+
private bool AddIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
27+
{
28+
var attribute = TryGetAttribute<IpRateLimiterAttribute>(httpContext);
29+
if (attribute.HasValue)
30+
{
31+
return holder.AddLimiter(TryGetLimiterHolder(httpContext, CreateKey(httpContext.Request.GetClientIpAddress(), attribute.Value.postfix!),
32+
attribute.Value.postfix!));
33+
}
34+
35+
return false;
36+
}
37+
38+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using ManagedCode.Orleans.RateLimiting.Client.Attributes;
3+
using ManagedCode.Orleans.RateLimiting.Client.Extensions;
4+
using ManagedCode.Orleans.RateLimiting.Core.Models.Holders;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Extensions.Logging;
7+
using Orleans;
8+
9+
namespace ManagedCode.Orleans.RateLimiting.Client.Middlewares;
10+
11+
public class OrleansUserRateLimitingMiddleware : OrleansBaseRateLimitingMiddleware
12+
{
13+
14+
protected OrleansUserRateLimitingMiddleware(ILogger logger, RequestDelegate next,
15+
IClusterClient client, IServiceProvider services) : base(logger, next, client, services)
16+
{
17+
}
18+
19+
20+
protected override void AddLimiters(HttpContext httpContext, GroupLimiterHolder holder)
21+
{
22+
AddAnonymousIpRateLimiter(httpContext, holder);
23+
24+
// if user is authenticated add in role limiter
25+
if (!AddInRoleIpRateLimiter(httpContext, holder))
26+
{
27+
// if user is not authenticated add authorized limiter
28+
AddAuthorizedIpRateLimiter(httpContext, holder);
29+
}
30+
}
31+
32+
private bool AddAnonymousIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
33+
{
34+
if (httpContext.User?.Identity?.IsAuthenticated is not true)
35+
{
36+
var attribute = TryGetAttribute<AnonymousIpRateLimiterAttribute>(httpContext);
37+
if (attribute.HasValue)
38+
{
39+
return holder.AddLimiter(TryGetLimiterHolder(httpContext, CreateKey(httpContext.Request.GetClientIpAddress(), attribute.Value.postfix!),
40+
attribute.Value.postfix!));
41+
}
42+
}
43+
44+
return false;
45+
}
46+
47+
private bool AddAuthorizedIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
48+
{
49+
if (httpContext.User?.Identity?.IsAuthenticated is true)
50+
{
51+
var attribute = TryGetAttribute<AuthorizedIpRateLimiterAttribute>(httpContext);
52+
if (attribute.HasValue)
53+
{
54+
return holder.AddLimiter(TryGetLimiterHolder(httpContext,
55+
CreateKey(httpContext.Request.GetClientIpAddress(), httpContext.User.Identity.Name!, attribute.Value.postfix!),
56+
attribute.Value.postfix!));
57+
}
58+
}
59+
60+
return false;
61+
}
62+
63+
private bool AddInRoleIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
64+
{
65+
var attribute = TryGetAttribute<InRoleIpRateLimiterAttribute>(httpContext);
66+
if (attribute.HasValue)
67+
{
68+
if (httpContext.User?.Identity?.IsAuthenticated is true && httpContext.User.IsInRole(attribute.Value.attribute.Role))
69+
{
70+
return holder.AddLimiter(TryGetLimiterHolder(httpContext,
71+
CreateKey(httpContext.Request.GetClientIpAddress(), httpContext.User.Identity.Name!, attribute.Value.attribute.Role, attribute.Value.postfix!),
72+
attribute.Value.postfix!));
73+
}
74+
}
75+
76+
return false;
77+
}
78+
79+
80+
}

0 commit comments

Comments
 (0)