Skip to content

Commit b544026

Browse files
author
John Luo
committed
Some feedback
1 parent 191575d commit b544026

File tree

6 files changed

+74
-30
lines changed

6 files changed

+74
-30
lines changed

src/Middleware/RequestLimiter/RequestLimiter.slnf

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
66
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
77
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
8+
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
89
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
910
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
1011
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
12+
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
1113
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
14+
"src\\Middleware\\HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj",
15+
"src\\Middleware\\RequestLimiter\\sample\\RequestLimiterSample.csproj",
16+
"src\\Middleware\\RequestLimiter\\src\\Microsoft.AspNetCore.RequestLimiter.csproj",
17+
"src\\ResourceLimits\\src\\System.Threading.ResourceLimits.csproj",
1218
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
1319
"src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj",
1420
"src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj",
15-
"src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj",
16-
"src\\http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
17-
"src\\http\\http\\src\\Microsoft.AspNetCore.Http.csproj",
18-
"src\\Middleware\\RequestLimiter\\sample\\RequestLimiterSample.csproj",
19-
"src\\Middleware\\RequestLimiter\\src\\Microsoft.AspNetCore.RequestLimiter.csproj",
20-
"src\\Middleware\\HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj"
21+
"src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj"
2122
]
2223
}
2324
}

src/Middleware/RequestLimiter/sample/Startup.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,37 +51,39 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<
5151
{
5252
await Task.Delay(5000);
5353
await context.Response.WriteAsync("Default!");
54-
}).EnforceLimit();
54+
}).EnforceDefaultRequestLimit();
5555

5656
endpoints.MapGet("/instance", async context =>
5757
{
5858
await Task.Delay(5000);
5959
await context.Response.WriteAsync("Hello World!");
60-
}).EnforceLimit(new TokenBucketRateLimiter(2, 2));
60+
}).EnforceRequestLimit(new TokenBucketRateLimiter(2, 2));
6161

6262
endpoints.MapGet("/concurrent", async context =>
6363
{
6464
await Task.Delay(5000);
6565
await context.Response.WriteAsync("Wrote!");
66-
}).EnforceConcurrencyLimit(concurrentRequests: 1);
66+
}).EnforceRequestConcurrencyLimit(concurrentRequests: 1);
6767

6868
endpoints.MapGet("/adhoc", async context =>
6969
{
7070
await Task.Delay(5000);
7171
await context.Response.WriteAsync("Tested!");
72-
}).EnforceRateLimit(requestPerSecond: 2);
72+
}).EnforceRequestRateLimit(requestPerSecond: 2);
7373

7474
endpoints.MapGet("/ipFromDI", async context =>
7575
{
7676
await Task.Delay(5000);
7777
await context.Response.WriteAsync("IP limited!");
78-
}).EnforceLimit("ipPolicy");
78+
}).EnforceRequestLimitPolicy("ipPolicy");
7979

8080
endpoints.MapGet("/multiple", async context =>
8181
{
8282
await Task.Delay(5000);
8383
await context.Response.WriteAsync("IP limited!");
84-
}).EnforceLimit("concurrency").EnforceLimit("rate");
84+
})
85+
.EnforceRequestLimitPolicy("ipPolicy")
86+
.EnforceRequestLimitPolicy("rate");
8587

8688
endpoints.MapControllerRoute(
8789
name: "default",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNetCore.RequestLimiter
7+
{
8+
// TODO: Double check ordering
9+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
10+
public class NoRequestLimitAttribute : Attribute { }
11+
}

src/Middleware/RequestLimiter/src/RequestLimiterEndpointExtensions.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.RequestLimiter
99
{
1010
public static class RequestLimiterEndpointExtensions
1111
{
12-
public static IEndpointConventionBuilder EnforceLimit(this IEndpointConventionBuilder builder)
12+
public static IEndpointConventionBuilder EnforceDefaultRequestLimit(this IEndpointConventionBuilder builder)
1313
{
1414
builder.Add(endpointBuilder =>
1515
{
@@ -19,7 +19,7 @@ public static IEndpointConventionBuilder EnforceLimit(this IEndpointConventionBu
1919
return builder;
2020
}
2121

22-
public static IEndpointConventionBuilder EnforceLimit(this IEndpointConventionBuilder builder, string policyName)
22+
public static IEndpointConventionBuilder EnforceRequestLimitPolicy(this IEndpointConventionBuilder builder, string policyName)
2323
{
2424
builder.Add(endpointBuilder =>
2525
{
@@ -29,7 +29,7 @@ public static IEndpointConventionBuilder EnforceLimit(this IEndpointConventionBu
2929
return builder;
3030
}
3131

32-
public static IEndpointConventionBuilder EnforceRateLimit(this IEndpointConventionBuilder builder, long requestPerSecond)
32+
public static IEndpointConventionBuilder EnforceRequestRateLimit(this IEndpointConventionBuilder builder, long requestPerSecond)
3333
{
3434
builder.Add(endpointBuilder =>
3535
{
@@ -43,7 +43,7 @@ public static IEndpointConventionBuilder EnforceRateLimit(this IEndpointConventi
4343
return builder;
4444
}
4545

46-
public static IEndpointConventionBuilder EnforceConcurrencyLimit(this IEndpointConventionBuilder builder, long concurrentRequests)
46+
public static IEndpointConventionBuilder EnforceRequestConcurrencyLimit(this IEndpointConventionBuilder builder, long concurrentRequests)
4747
{
4848
builder.Add(endpointBuilder =>
4949
{
@@ -56,10 +56,10 @@ public static IEndpointConventionBuilder EnforceConcurrencyLimit(this IEndpointC
5656
return builder;
5757
}
5858

59-
public static IEndpointConventionBuilder EnforceLimit(this IEndpointConventionBuilder builder, ResourceLimiter limiter)
60-
=> builder.EnforceLimit((HttpContextLimiter)limiter);
59+
public static IEndpointConventionBuilder EnforceRequestLimit(this IEndpointConventionBuilder builder, ResourceLimiter limiter)
60+
=> builder.EnforceRequestLimit((HttpContextLimiter)limiter);
6161

62-
public static IEndpointConventionBuilder EnforceLimit(this IEndpointConventionBuilder builder, AggregatedResourceLimiter<HttpContext> limiter)
62+
public static IEndpointConventionBuilder EnforceRequestLimit(this IEndpointConventionBuilder builder, AggregatedResourceLimiter<HttpContext> limiter)
6363
{
6464

6565
builder.Add(endpointBuilder =>

src/Middleware/RequestLimiter/src/RequestLimiterMiddleware.cs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Threading.ResourceLimits;
78
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.Http;
@@ -24,28 +25,30 @@ public RequestLimiterMiddleware(RequestDelegate next, ILoggerFactory loggerFacto
2425
_options = options.Value;
2526
}
2627

27-
public async Task Invoke(HttpContext context)
28+
public Task Invoke(HttpContext context)
2829
{
2930
_logger.LogInformation("Resource limiting: " + context.Request.Path);
3031

3132
var endpoint = context.GetEndpoint();
3233
var attributes = endpoint?.Metadata.GetOrderedMetadata<RequestLimitAttribute>();
34+
var noLimitAttributes = endpoint?.Metadata.GetOrderedMetadata<NoRequestLimitAttribute>();
3335

34-
if (attributes == null)
36+
if (attributes == null || noLimitAttributes != null)
3537
{
36-
await _next.Invoke(context);
37-
return;
38+
return _next.Invoke(context);
3839
}
3940

41+
return InvokeAsync(context, attributes);
42+
}
43+
private async Task InvokeAsync(HttpContext context, IReadOnlyList<RequestLimitAttribute> attributes)
44+
{
4045
var resourceLeases = new Stack<ResourceLease>();
4146
try
4247
{
4348
foreach (var attribute in attributes)
4449
{
45-
if (!string.IsNullOrEmpty(attribute.Policy) && attribute.Limiter != null)
46-
{
47-
throw new InvalidOperationException("Cannot specify both policy and limiter");
48-
}
50+
// At most one of Policy or Limiter can be set.
51+
Debug.Assert(string.IsNullOrEmpty(attribute.Policy) || attribute.Limiter == null);
4952

5053
if (string.IsNullOrEmpty(attribute.Policy) && attribute.Limiter == null)
5154
{
@@ -97,11 +100,38 @@ public async Task Invoke(HttpContext context)
97100
}
98101
};
99102
}
100-
101-
private async Task<bool> ApplyLimitAsync(AggregatedResourceLimiter<HttpContext> limiter, HttpContext context, Stack<ResourceLease> obtainedResources)
103+
private Task<bool> ApplyLimitAsync(AggregatedResourceLimiter<HttpContext> limiter, HttpContext context, Stack<ResourceLease> obtainedResources)
102104
{
103105
_logger.LogInformation("Resource count: " + limiter.EstimatedCount(context));
104-
var resourceLease = await limiter.WaitAsync(context);
106+
var resourceLeaseTask = limiter.WaitAsync(context);
107+
108+
if (resourceLeaseTask.IsCompletedSuccessfully)
109+
{
110+
var resourceLease = resourceLeaseTask.Result;
111+
if (!resourceLease.IsAcquired)
112+
{
113+
_logger.LogInformation("Resource exhausted");
114+
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
115+
return OnRejectAsync(context, resourceLease);
116+
}
117+
118+
_logger.LogInformation("Resource obtained");
119+
obtainedResources.Push(resourceLease);
120+
return Task.FromResult(true);
121+
}
122+
123+
return ApplyLimitAsyncAwaited(resourceLeaseTask, context, obtainedResources);
124+
}
125+
126+
private async Task<bool> OnRejectAsync(HttpContext context, ResourceLease resourceLease)
127+
{
128+
await _options.OnRejected(context, resourceLease);
129+
return false;
130+
}
131+
132+
private async Task<bool> ApplyLimitAsyncAwaited(ValueTask<ResourceLease> resourceLeaseTask, HttpContext context, Stack<ResourceLease> obtainedResources)
133+
{
134+
var resourceLease = await resourceLeaseTask;
105135
if (!resourceLease.IsAcquired)
106136
{
107137
_logger.LogInformation("Resource exhausted");

0 commit comments

Comments
 (0)