【ASP.NET Core Web】レートリミットを実装する

ネコニウム研究所

PCを利用したモノづくりに関連する情報や超個人的なナレッジを掲載するブログ

【ASP.NET Core Web】レートリミットを実装する

2024-4-22 | ,

ASP .NET Core Webでレートリミットを実装したい!

概要

今回の記事では、ASP .NET Core Webでミドルウェアを使ってレートリミットを実装する手順を掲載する。

レートリミットとは、例えば一定時間内でAPIサーバーへのリクエスト回数を制限して、サーバーへの負荷が大きくなりすぎないようにするやり方のひとつ。

仕様書

環境

  • .NET 8

手順書

何に対してどうレートリミットをかけるかのか、いろいろなやり方があるんだけども、この記事ではクライアントIPのアクセスに対して、Sliding Log方式でレートリミットを実装した。

レートリミットを実装したクラスRateLimiterMiddlewareは下記のような感じになった。

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

public class RateLimiterMiddleware
{
    private readonly RequestDelegate next;
    private readonly int maxRequests;
    private readonly TimeSpan timeSpan;
    private static ConcurrentDictionary<string, ConcurrentQueue<DateTime>> _requestLogs = new ConcurrentDictionary<string, ConcurrentQueue<DateTime>>();

    public RateLimiterMiddleware(RequestDelegate next, int maxRequests, TimeSpan timeSpan)
    {
        this.next = next;
        this.maxRequests = maxRequests;
        this.timeSpan = timeSpan;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var clientIp = context.Connection.RemoteIpAddress?.ToString();
        if (clientIp == null)
        {
            await next(context);
            return;
        }

        var requestLog = _requestLogs.GetOrAdd(clientIp, new ConcurrentQueue<DateTime>());
        var now = DateTime.UtcNow;

        lock (requestLog)
        {
            while (requestLog.TryPeek(out var timestamp) && (now - timestamp) > timeSpan)
            {
                requestLog.TryDequeue(out _);
            }

            requestLog.Enqueue(now);

            if (requestLog.Count > maxRequests)
            {
                context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                context.Response.WriteAsync("Rate limit exceeded! Please try again later!!");
                return;
            }
        }

        await next(context);
    }
}

Program.csRateLimiterMiddlewareの設定を追加する。

...

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        ...

        app.UseMiddleware<RateLimiterMiddleware>(10, TimeSpan.FromMinutes(5));

        ...

        app.Run();
    }
}

第1引数は一定時間内にリクエストできる回数。
第2引数はどのくらい遡ってリクエストの回数をカウントするかの時間。
例では5分間に10回を超えるとステータスコード429を返す感じ。

特定のアドレスにレートリミットをかけたい場合の例。

...

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        ...

        app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), builder =>
        {
            builder.UseMiddleware<RateLimiterMiddleware>(10, TimeSpan.FromMinutes(5));
        });

        ...

        app.Run();
    }
}

example.com/api以下のURLへのリクエストにレートリミットが掛かるようになる。

まとめ(感想文)

サーバーの負荷をコントールしたい時や従量課金のクラウドを使うとコストを抑えれる時に使えるかもね!