返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
Asp-Net-Core开发笔记

ASP.NET Core Development Notes: Using Native Rate Limiting for Interfaces

I gave it a try, but it still feels like too much hassle. I'll just switch back to the original AspNetCoreRateLimit component.

曦远 (DealiAxy)约 19 分钟
目录12 个章节

Preface

Previously, I introduced using the AspNetCoreRateLimit component for API rate limiting (接口限流).

Starting from .Net7, AspNetCore began to include a built-in rate-limiting component. At that time, our project was still on .Net6, so we could only use a third-party one.

Now that we have upgraded to .Net8, it's time to try out this native component.

After trying it out: Configuration and usage are quite simple, but its functionality isn't as flexible as AspNetCoreRateLimit.

Registering Services

To keep the code in Program.cs concise, we continue to use extension methods for service registration and configuration.

In the file src/IdsLite.Api/Extensions/CfgRateLimit.cs:

public static class RateLimitPolicies {
  public const string Fixed = "fixed";
  public const string Sliding = "sliding";
}

public static class CfgRateLimit {
  public static IServiceCollection AddRateLimit(this IServiceCollection services) {
    services.AddRateLimiter(options => {
      options.AddFixedWindowLimiter(RateLimitPolicies.Fixed, opt => {
        opt.Window = TimeSpan.FromMinutes(1); // Time window
        opt.PermitLimit = 3; // Maximum number of requests allowed in the time window
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // Request processing order
        opt.QueueLimit = 2; // Maximum number of requests allowed in the queue
      });
    });

    return services;
  }

  public static IApplicationBuilder UseRateLimit(this IApplicationBuilder app) {
    app.UseRateLimiter();
    return app;
  }
}

Here, the Fixed Window strategy is used for rate limiting (限流). Specific configurations are commented in the code.

PS: Regarding the four commonly used rate-limiting strategies (Fixed/Sliding Window, Token Bucket, Concurrency), they have been introduced in the previous article.

Next, add the following to the Program.cs file:

builder.Services.AddRateLimit();

Configure the middleware:

var app = builder.Build();

app.MapHealthChecks("/healthz");
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseExceptionless();

app.UseHttpsRedirection();
app.UseRouting(); // Routing should be defined before applying rate limiting
app.UseRateLimiter(); // Rate limiting should be applied after routing
app.UseCors(); // CORS should be applied after rate limiting
app.UseAuthentication(); // Authentication should be applied before authorization
app.UseAuthorization(); // Authorization should be applied after authentication

app.UseSwaggerWithAuthorize();
app.MapControllers();

app.Run();

Note: You must place the rate-limiting middleware immediately after UseRouting.

Since we need to add rate-limiting policies to specific endpoints, the request must first be routed to the designated endpoint before rate limiting is applied.

Adding Rate Limiting Configuration to Interfaces

To add rate limiting to a Controller or interface method that requires it, simply add [EnableRateLimiting(RateLimitPolicies.Fixed)].

For example, this login interface:

[HttpPost("login/password")]
[EnableRateLimiting(RateLimitPolicies.Fixed)]
[ValidateClient(ClientIdSource.Body, ParameterName = "ClientId")]
public async Task<IActionResult> LoginByPassword(PwdLoginDto dto) {}

Test Results

Test the API interface using curl in the shell.

for i in {1..11}; do curl http://localhost:5000/api/auth/login/password; done

With the configuration above, the rate-limiting policy should be triggered on the 11th request, causing it to hang until it returns after 1 minute.

This is because we configured opt.QueueLimit = 2, meaning that when rate limiting is triggered, 2 requests can be queued, and these 2 requests will wait until the system can regenerate a response.

If a 3rd request comes in, it will immediately return a 503 (Service Unavailable) status. The component returns 503 by default, but you can configure it to return 429 (Too many requests) instead.

Configuration

Configuring the Wait Queue

Setting opt.QueueLimit to 0 immediately rejects responses when rate limiting is triggered.

Custom Rejection Response

Previously, we mentioned that triggering rate limiting returns a 503 status. We can also configure it to return 429 ourselves.

services.AddRateLimiter(options => {
  options.OnRejected = (context, token) => {
    // 检查 context.Lease 中是否包含 RetryAfter 元数据
    // RetryAfter 元数据通常指示客户端应该在多少秒后重试请求
    if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) {
      // 如果 RetryAfter 元数据存在,将 RetryAfter 值(以秒为单位)添加到响应头中
      context.HttpContext.Response.Headers.RetryAfter =
        ((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
    }

    context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
    context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.",
                                            cancellationToken: token);

    return new ValueTask();
  };
});

In fact, this is the approach shown in the official documentation. The most critical line is context.HttpContext.Response.StatusCode; the rest is just an added bonus and can be omitted.

Extensions

IP Address-Based Rate Limiting

Previously, when I used the AspNetCoreRateLimit component, I directly utilized its built-in IP address rate limiting functionality.

In the native Microsoft.AspNetCore.RateLimiting, this feature is not built-in and needs to be implemented manually.

I looked into the approach described in the documentation. The idea is to create a partition for each IP address (PartitionedRateLimiter.Create) and then configure the rate limiting strategy within each partition.

For details, refer to the limiter-with-onrejected-retryafter-and-globallimiter section in the official documentation.

This implementation approach also needs to consider that the application may be vulnerable to denial-of-service attacks using IP source address spoofing. See the reference materials for more details.

PS: It's a bit complicated. I might just go back to using AspNetCoreRateLimit. What a hassle.

Rate Limiting Based on Users (根据用户进行限流)

Through a custom policy, you can implement rate limiting based on logged-in users, for example, each user can only make 10 requests per minute.

For details, refer to the limiter-with-authorization section of the official documentation.

By the way, the official documentation also mentions the dotnet user-jwts tool. This is the first time I've heard of it, so I'll mark it to check it out later.

Summary

That's about it. After trying it out, I found it to be too much hassle, so I'm sticking with the original AspNetCoreRateLimit component.

Reference Materials

_EOF_

ASP.NET Core Development Notes: Using Native Rate Limiting for Interfaces

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

Asp-Net-Core开发笔记:FrameworkDependent搭配docker部署

之前我写过一篇使用 docker 部署 AspNetCore 应用的文章,这种方式搭配 CICD 非常方便, build 之后 push 到私有的 dockerhub ,在生产服务器上 pull 下来镜像就可以直接运行了。 然而,有时需要一种更传统的部署方式,比如在本地打包可执行文件之后直接放到服务器上执行。 这种方式配置完成之后,每次更新只需要重新本地重新 publish 后把可执行文件上传

2023-09-08 · Asp-Net-Core开发笔记 · 约 1 分钟

Asp-Net-Core开发笔记:使用NPM和gulp管理前端静态文件

前言 本文介绍的是AspNetCore的MVC项目,WebApi+独立前端这种前后端分离的项目就不需要多此一举了~默认前端小伙伴是懂得使用前端工具链的。 为啥要用MVC这种服务端渲染技术呢? 简单项目不需要强行分离增加复杂度(如:我正在开发的博客项目) 后端渲染利于SEO,对博客网站友好 OK,虽然MVC的技术老了点,但依然可以结合现代前端工具链来提高效率 本文的食用需要先安装好Node.js环境

2022-02-17 · Asp-Net-Core开发笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…

ASP.NET Core Development Notes: Using Native Rate Limiting for Interfaces · StarBlog