Skip to content

Commit 4d27258

Browse files
committed
Attrubtes
1 parent 639bf57 commit 4d27258

55 files changed

Lines changed: 793 additions & 87 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace ManagedCode.Orleans.RateLimiting.Client.Attributes;
4+
5+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
6+
public class AnonymousIpRateLimiterAttribute : Attribute, IRateLimiterAttribute
7+
{
8+
public string ConfigurationName { get; }
9+
10+
public AnonymousIpRateLimiterAttribute(string configurationName)
11+
{
12+
ConfigurationName = configurationName;
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace ManagedCode.Orleans.RateLimiting.Client.Attributes;
4+
5+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
6+
public class AuthorizedIpRateLimiterAttribute : Attribute, IRateLimiterAttribute
7+
{
8+
public string ConfigurationName { get; }
9+
10+
public AuthorizedIpRateLimiterAttribute(string configurationName)
11+
{
12+
ConfigurationName = configurationName;
13+
}
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace ManagedCode.Orleans.RateLimiting.Client.Attributes;
2+
3+
public interface IRateLimiterAttribute
4+
{
5+
string ConfigurationName { get; }
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace ManagedCode.Orleans.RateLimiting.Client.Attributes;
4+
5+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
6+
public class IpRateLimiterAttribute : Attribute, IRateLimiterAttribute
7+
{
8+
public string ConfigurationName { get; }
9+
10+
public IpRateLimiterAttribute(string configurationName)
11+
{
12+
ConfigurationName = configurationName;
13+
}
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using ManagedCode.Orleans.RateLimiting.Client.Middlewares;
2+
using Microsoft.AspNetCore.Builder;
3+
4+
namespace ManagedCode.Orleans.RateLimiting.Client.Extensions;
5+
6+
public static class ApplicationBuilderExtensions
7+
{
8+
public static IApplicationBuilder AddOrleansRateLimiting(this IApplicationBuilder applicationBuilder)
9+
{
10+
applicationBuilder.UseMiddleware<RateLimitingMiddleware>();
11+
return applicationBuilder;
12+
}
13+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
13
using Orleans.Hosting;
4+
using Orleans.Runtime;
25

36
namespace ManagedCode.Orleans.RateLimiting.Client.Extensions;
47

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.Extensions.Primitives;
5+
6+
namespace ManagedCode.Orleans.RateLimiting.Client.Extensions;
7+
8+
public static class HttpRequestExtensions
9+
{
10+
public static string GetClientIpAddress(this HttpRequest request)
11+
{
12+
return GetClientIpAddress(request, new []
13+
{
14+
"X-Real-IP",
15+
"X-Forwarded-For",
16+
"REMOTE_ADDR"
17+
});
18+
}
19+
20+
public static string GetClientIpAddress(this HttpRequest request, string[] headers)
21+
{
22+
string? ip = null;
23+
24+
foreach (var header in headers)
25+
{
26+
ip = GetHeaderValueAs(request, header);
27+
if(!string.IsNullOrEmpty(ip))
28+
break;
29+
}
30+
31+
if (string.IsNullOrEmpty(ip) && request.HttpContext?.Connection?.RemoteIpAddress != null)
32+
ip = request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty;
33+
34+
return ip ?? string.Empty;
35+
}
36+
37+
private static string GetHeaderValueAs(HttpRequest request, string headerName)
38+
{
39+
StringValues values;
40+
41+
if (request.Headers?.TryGetValue(headerName, out values) ?? false)
42+
{
43+
string rawValues = values.ToString(); // writes out as Csv when there are multiple.
44+
45+
if (!string.IsNullOrWhiteSpace(rawValues))
46+
{
47+
var value = SplitCsv(rawValues).FirstOrDefault();
48+
return value ?? string.Empty;
49+
}
50+
}
51+
52+
return string.Empty;
53+
}
54+
55+
private static IEnumerable<string> SplitCsv(string? csvList)
56+
{
57+
if (string.IsNullOrWhiteSpace(csvList))
58+
return Enumerable.Empty<string>();
59+
60+
return csvList
61+
.TrimEnd(',')
62+
.Split(',')
63+
.Select(s => s.Trim());
64+
}
65+
}

ManagedCode.Orleans.RateLimiting.Client/Middlewares/RateLimitingHubFilter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public RateLimitingHubFilter(ILogger<RateLimitingHubFilter> logger, IClusterClie
1717
_logger = logger;
1818
_client = client;
1919
}
20+
2021
public async ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
2122
{
2223
var limiter = _client.GetFixedWindowRateLimiter(invocationContext.Context.User.Identity.Name);
Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
using System;
2+
using System.Collections.Generic;
13
using System.Diagnostics;
4+
using System.Reflection;
25
using System.Threading.Tasks;
6+
using ManagedCode.Orleans.RateLimiting.Client.Attributes;
7+
using ManagedCode.Orleans.RateLimiting.Client.Extensions;
38
using ManagedCode.Orleans.RateLimiting.Core.Extensions;
9+
using ManagedCode.Orleans.RateLimiting.Core.Models;
10+
using ManagedCode.Orleans.RateLimiting.Core.Models.Holders;
11+
using Microsoft.AspNetCore.Authorization;
412
using Microsoft.AspNetCore.Http;
513
using Microsoft.AspNetCore.Http.Extensions;
14+
using Microsoft.AspNetCore.Mvc.Controllers;
15+
using Microsoft.Extensions.DependencyInjection;
616
using Microsoft.Extensions.Logging;
717
using Orleans;
818

@@ -13,21 +23,87 @@ public class RateLimitingMiddleware
1323
private readonly ILogger<RateLimitingMiddleware> _logger;
1424
private readonly RequestDelegate _next;
1525
private readonly IClusterClient _client;
26+
private readonly IServiceProvider _services;
1627

17-
public RateLimitingMiddleware(ILogger<RateLimitingMiddleware> logger, RequestDelegate next, IClusterClient client)
28+
public RateLimitingMiddleware(ILogger<RateLimitingMiddleware> logger, RequestDelegate next, IClusterClient client, IServiceProvider services)
1829
{
1930
_logger = logger;
2031
_next = next;
2132
_client = client;
33+
_services = services;
2234
}
2335

2436
public async Task Invoke(HttpContext httpContext)
2537
{
26-
var limiter = _client.GetFixedWindowRateLimiter(httpContext.User.Identity.Name);
38+
await using var holder = new GroupLimiterHolder();
2739

28-
await using var lease = await limiter.AcquireAsync();
29-
lease.ThrowIfNotAcquired();
40+
AddIpRateLimiter(httpContext, holder);
41+
AddAnonymousIpRateLimiter(httpContext, holder);
42+
AddAuthorizedIpRateLimiter(httpContext, holder);
43+
44+
await holder.AcquireAsync();
3045
await _next(httpContext);
46+
}
47+
48+
49+
void AddIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
50+
{
51+
holder.AddLimiter(TryGetLimiterHolder<IpRateLimiterAttribute>(httpContext, httpContext.Request.GetClientIpAddress()));
52+
}
53+
54+
void AddAnonymousIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
55+
{
56+
if(httpContext.User?.Identity?.IsAuthenticated is not true)
57+
holder.AddLimiter(TryGetLimiterHolder<AnonymousIpRateLimiterAttribute>(httpContext, httpContext.Request.GetClientIpAddress()));
58+
}
59+
60+
void AddAuthorizedIpRateLimiter(HttpContext httpContext, GroupLimiterHolder holder)
61+
{
62+
if(httpContext.User?.Identity?.IsAuthenticated is true)
63+
holder.AddLimiter(TryGetLimiterHolder<AuthorizedIpRateLimiterAttribute>(httpContext,
64+
CreateKey(httpContext.User.Identity.Name,httpContext.Request.GetClientIpAddress())));
3165

3266
}
67+
68+
69+
private ILimiterHolder? TryGetLimiterHolder<T>(HttpContext httpContext, string key) where T : Attribute, IRateLimiterAttribute
70+
{
71+
var endpoint = httpContext.GetEndpoint();
72+
73+
if(endpoint is null)
74+
return null;
75+
76+
// first try to get attribute from endpoint,
77+
var attribute = endpoint.Metadata.GetMetadata<T>();
78+
string postfix = endpoint.ToString()!;
79+
80+
if (attribute is null)
81+
{
82+
// then try to get attribute from controller
83+
var controllerType = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>()?.ControllerTypeInfo;
84+
85+
if (controllerType != null)
86+
{
87+
attribute = controllerType.GetCustomAttribute<T>(inherit: true);
88+
postfix = controllerType.ToString();
89+
}
90+
}
91+
92+
if (attribute != null)
93+
{
94+
var limiter = _client.GetRateLimiterByConfig(CreateKey(key,postfix), attribute.ConfigurationName, _services.GetService<IEnumerable<RateLimiterConfig>>());
95+
96+
if(limiter is null)
97+
_logger.LogError($"Configuration {attribute.ConfigurationName} not found for RateLimiter");
98+
99+
return limiter;
100+
}
101+
102+
return null;
103+
}
104+
105+
string CreateKey(string key, string postfix)
106+
{
107+
return $"{key}:{postfix}";
108+
}
33109
}

ManagedCode.Orleans.RateLimiting.Core/Attributes/ConcurrencyLimiterAttribute.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace ManagedCode.Orleans.RateLimiting.Core.Attributes;
66
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
77
public class ConcurrencyLimiterAttribute : Attribute, ILimiterAttribute<ConcurrencyLimiterOptions>
88
{
9+
public string? ConfigurationName { get; }
910
public string? Key { get; }
1011
public KeyType KeyType { get; }
1112
public ConcurrencyLimiterOptions? Options { get; }
@@ -50,4 +51,17 @@ public ConcurrencyLimiterAttribute(KeyType keyType = KeyType.GrainId, string key
5051
};
5152
}
5253
}
54+
55+
public ConcurrencyLimiterAttribute(string configurationName, KeyType keyType = KeyType.GrainId, string key = default)
56+
{
57+
ConfigurationName = configurationName;
58+
Key = key;
59+
KeyType = keyType;
60+
61+
//override keyType if key is set
62+
if (!string.IsNullOrEmpty(key))
63+
{
64+
KeyType = KeyType.Key;
65+
}
66+
}
5367
}

0 commit comments

Comments
 (0)