返回文章列表文章索引阅读全文
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开发笔记:接口返回json对象出现套娃递归问题

前言 看了下推送记录,一个月前,OK,我又变成月更了o(╯□╰)o,这绝对不行![○・`Д´・ ○] 所以今天来更新了 其实不是我懒得更新或者是太忙,其实是最近在写一篇很长的博客,一直没写完( Ĭ ^ Ĭ ) 好吧,先进入正题…… 有一个关于WebApi序列化的问题,跟设计有关,但在涉及到关联字段的时候经常会遇到。 实体类 先看看实体类定义,限于篇幅,只保留几个关键字段。 public class

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

Asp-Net-Core开发笔记:在docker部署时遇到一个小坑

哦吼 之前刚说了尝试了使用docker来部署AspNetCore应用,结果这才刚上班就遇到问题了= =… 我这项目用的数据库是Oracle,之前直接运行没啥问题,但放在docker里运行就报了这个错误: ORA-01882: timezone region not found 看这错误信息是在说时区不对,分别在服务器和docker里看一下时间: [root@localhost ~]# date -

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

Asp-Net-Core开发笔记:集成Hangfire实现异步任务队列和定时任务

前言 最近把Python写的数据采集平台往.Net Core上迁移,原本的采集任务使用多进程+线程池的方式来加快采集速度,使用Celery作为异步任务队列兼具定时任务功能,这套东西用着还行,但反正就折腾嘛,直接上C#~ 本文记录 Hangfire 在实际应用里的用法,我发现网络上找到的大部分文章都是用 Hangfire 的异步任务输出个 Hello World,然后就没了。我实在不知道这样的文章写

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

讨论

共 0 条

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

加载评论中…