Asp-Net-Core開發筆記:使用原生的介面限流功能

程序设计实验室發表於2024-05-22

前言

之前介紹過使用 AspNetCoreRateLimit 元件來實現介面限流

從 .Net7 開始,AspNetCore 開始內建限流元件,當時我們的專案還在 .Net6 所以只能用第三方的

現在都升級到 .Net8 了,當然是得來試試這個原生元件

體驗後:配置使用都比較簡單,不過功能也沒有 AspNetCoreRateLimit 那麼靈活

註冊服務

為了保持 Program.cs 的程式碼簡潔,依然是使用擴充套件方法來實現服務註冊和配置

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); // 時間視窗
        opt.PermitLimit = 3; // 在時間視窗內允許的最大請求數
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // 請求處理順序
        opt.QueueLimit = 2; // 佇列中允許的最大請求數
      });
    });

    return services;
  }

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

這裡使用了 Fixed Window (固定視窗)的策略來進行限流,具體配置程式碼裡面有註釋。

PS: 關於四種常用的限流策略(固定/滑動視窗、令牌桶、併發),上一篇文章已經介紹過了

接著在 Program.cs 檔案中新增

builder.Services.AddRateLimit();

配置中介軟體

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();

注意這裡一定要把限流中介軟體放在 UseRouting 之後的第一個

因為我們要在具體的介面上新增限流策略,所以要在請求已經被路由到指定的介面之後再進行限流

為介面新增限流配置

在需要限流的 Controller 或者介面方法上新增 [EnableRateLimiting(RateLimitPolicies.Fixed)] 就可以了

比如這個登入介面

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

測試效果

在 shell 中使用 curl 進行介面測試

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

按照上述的配置的話,在第11個請求的時候,觸發了限流策略,應該會卡住,等到1分鐘後才能返回

因為我們配置了 opt.QueueLimit = 2 即觸發限流時,可以有 2 個請求在佇列裡排隊,這 2 個請求會一直等到系統可以重新生成響應為止。

如果有第 3 個請求進來,則立刻返回 503 (Service Unavailable) ,這個元件預設就是 503 ,也可以自己配置成 429 (Too many requests)

配置

配置等待佇列

opt.QueueLimit 設定為 0 ,可以實現觸發限流立刻拒絕響應

自定義拒絕響應

前面說觸發限流返回503,我們也可以自己配置成 429

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();
  };
});

其實這個是官方文件裡的寫法,最關鍵的就是 context.HttpContext.Response.StatusCode 這一行,其他的都是錦上添花,可以省略。

擴充套件

根據IP地址進行限流

之前我使用 AspNetCoreRateLimit 元件的時候,直接使用了它提供的 IP 地址限流功能

在原生的 Microsoft.AspNetCore.RateLimiting 中,沒有內建這個功能,需要自己來實現

我看了下文件裡介紹的,思路是為每一個 IP 地址建立一個分割槽 (PartitionedRateLimiter.Create),在每個分割槽裡再配置限流策略

詳見官方文件的 limiter-with-onrejected-retryafter-and-globallimiter 部分

這種實現方式還要考慮一下,應用容易受到採用 IP 源地址欺騙的拒絕服務攻擊,詳見參考資料

PS: 有點複雜,我還是用回 AspNetCoreRateLimit 得了,真是折騰

根據使用者進行限流

透過自定義策略,可以實現根據登入使用者進行限流,比如每個使用者每分鐘只能請求 10 次這樣子

詳見官方文件的 limiter-with-authorization 部分

對了,官方文件裡還提到了 dotnet user-jwts 這個工具,第一次聽到,先 mark 一下,後面來看看咋樣

小結

就這樣吧,試用了一下,感覺還是太折騰,用回原來的 AspNetCoreRateLimit 元件得了

參考資料

  • https://learn.microsoft.com/zh-cn/aspnet/core/performance/rate-limit

  • https://www.rfc-editor.org/info/bcp38

  • https://learn.microsoft.com/zh-cn/aspnet/core/security/authentication/jwt-authn?view=aspnetcore-8.0

相關文章