2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ExpressにおけるRate Limitの実装ガイド

Last updated at Posted at 2024-02-25

Rate Limitの概要

Rate Limitは、APIへのリクエスト数を制御するためのシステムです。
これは、APIを過剰に使用したり、悪意のある攻撃を防ぐために使用されます。
Rate Limitによって、一定期間内に許可されるリクエストの数が限定されることで、サーバーの過負荷を防ぎ、サービス品質を維持することができます。

Rate Limitの利点

サービスの過負荷防止

APIへの過剰なリクエストがサーバーのリソースを圧迫し、サービスが遅くなったり、最悪の場合はダウンしたりすることを防ぎます。
Rate Limitによって、予期せぬトラフィックの急増による障害からAPIを守ります。

悪意のある利用の防止

DDoS攻撃など、悪意のあるユーザーによる過剰なリクエストを防ぎます。
これにより、APIが安全に運用されることを確保します。

公平なリソースの利用

全てのユーザーに対して公平にAPIリソースへのアクセスを提供します。
一部のユーザーによる過剰なリクエストが他のユーザーの利用機会を奪うことを防ぎます。

コスト管理

クラウドサービスなどのリソースを使用するAPIでは、リクエスト数に応じてコストが発生します。
Rate Limitによってリクエスト数を制御することで、運用コストの急増を防ぐことができます。

express-rate-limit

express-rate-limitは、Node.jsのExpressアプリケーションに簡単にRate Limitを追加するためのミドルウェアです。
このライブラリは、APIエンドポイントへの過剰なアクセスを防ぎ、アプリケーションの負荷を軽減するために設計されています。
特に、WebサービスやAPIがDDoS攻撃やスクレイピングなどの悪意あるトラフィックに晒されるリスクを低減させます。

主な特徴

簡単なセットアップ

express-rate-limitは、数行のコードを追加するだけでExpressアプリケーションにRate Limitを実装できます。

カスタマイズ

リクエスト数の制限、時間窓、レスポンスのカスタマイズなど、多くのオプションを通じて挙動を細かく調整できます。

メモリストア組み込み

デフォルトでメモリストアを使用してリクエスト数を追跡しますが、Redisなどの外部ストアを組み込むことも可能です。

使用方法

必要なパッケージのインストール

まず、Expressexpress-rate-limitをインストールします。
ターミナルで以下のコマンドを実行します。

$ npm init -y
$ npm install express express-rate-limit typescript @types/node @types/express
$ npx tsc --init

Expressアプリケーションの基本的なセットアップ

以下は、TypeScriptを使用したコードの例です。
APIエンドポイントに対して、15分間に10リクエストまでの制限を設けます。

index.ts
import express from 'express';
import rateLimit from 'express-rate-limit';

// Rate Limitの設定
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  limit: 10, // 15分間に10リクエストまで許可
  standardHeaders: true, // Rate Limitヘッダーに関する情報を返す
  legacyHeaders: false, // 無効化されたRate Limitヘッダーを削除する
});

const app = express();

// APIルートにRate Limitを適用
app.use("/api/", apiLimiter);

// テスト用のAPIエンドポイント
app.get("/api/test", (req, res) => {
  res.json({ message: "Rate limit test API" });
});

// サーバーを起動
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

コンパイルと実行

index.tsをコンパイルし、index.jsを生成します。

$ npx tsc

生成されたJavaScriptファイルを実行します。

$ node index.js
Server running on port 3000

これで、Rate Limitが設定されたExpress APIサーバーを起動することができました。

Rate Limitの確認

localhost:3000/api/testにアクセスします。
最初の10回は通常通りの応答が得られます。
confirm.png
11回目以降は制限に達したことを示す応答が返されるようになります。
これにより、Rate Limitの動作を確認できます。
confirm-rate-limit.png

オプション

express-rate-limitのオプションを説明します。

  • windowMs
    • 説明: リクエストを記憶しておく期間(ミリ秒単位)
    • デフォルト値: 60000 ms(1分)
  • limit
    • 説明: windowMsの期間中にクライアントが許可される最大リクエスト数
    • デフォルト値: 5
  • message
    • 説明: クライアントがRate Limitに達した際、返されるレスポンスボディ
    • デフォルト値: 'Too many requests, please try again later.'
  • statusCode
    • 説明: クライアントがRate Limitに達した際、返されるHTTPステータスコード
    • デフォルト値: 429(HTTP 429 Too Many Requests)
  • standardHeaders
    • 説明: 標準化されたRate Limitヘッダー(RateLimit-*)を送信するかどうか
    • デフォルト値: false(後方互換性のため)
  • legacyHeaders
    • 説明: X-RateLimit-*ヘッダーをRate Limit情報と共に送信するかどうか
    • デフォルト値: true(後方互換性のため)
  • keyGenerator
    • 説明: クライアントを識別するカスタムキーを生成するメソッド
    • デフォルト値: クライアントのIPアドレス
  • handler
    • 説明: クライアントがRate Limitに達した際に実行されるExpressリクエストハンドラー
    • デフォルト値: 設定されたstatusCodemessage
  • store
    • 説明: クライアントのヒット数を格納するために使用するストア
    • デフォルト値: MemoryStore
  • skipFailedRequests
    • 説明: trueに設定すると、4XXまたは5XXのステータスコードを持つレスポンスに対するリクエストは、クライアントのクォータにカウントされません
    • デフォルト値: false
  • skipSuccessfulRequests
    • 説明: trueに設定すると、ステータスコードが400未満の成功したリクエストは、クライアントのクォータにカウントされません
    • デフォルト値: false
  • skip
    • 説明: 特定のリクエストがRate Limitのクォータにカウントされるかどうかを決定する。このオプションがtrueを返すと、そのリクエストはカウントされません
    • デフォルト値: false

これらのオプションを適切に設定することで、express-rate-limitを使用してアプリケーションのRate Limit要件に合わせたカスタマイズが可能になります。

keyGeneratorのカスタマイズ

keyGeneratorオプションは、リクエストを行っているクライアントを一意に識別するために使用されます。
Requestオブジェクトから一意のキー(識別子)を生成し、そのキーを用いて各クライアントのリクエスト数をカウントします。
これにより、指定された時間窓(windowMsで設定)内に許可された最大リクエスト数(limitで設定)を超えたクライアントに対してRate Limitを適用することができます。

keyGeneratorの役割

keyGeneratorは、Requestオブジェクトを引数として受け取り、文字列を返す必要があります。
デフォルトでは、クライアントのIPアドレスがキーとして使用されますが、keyGeneratorをカスタマイズすることで、例えばユーザーID、APIキー、またはその他の識別情報を基にしたキーを生成することが可能です。

カスタマイズ例

カスタマイズしたkeyGeneratorを使用して、認証されたユーザーIDに基づいてRate Limitを適用する例は以下の通りです。

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  limit: 100, // 各ユーザーごとに15分間に100リクエストまで許可
  keyGenerator: (req) => {
    // 認証されたユーザーのIDを基にキーを生成
    return req.user.id;
  },
  // その他のオプション...
});

このようにkeyGeneratorをカスタマイズすることで、express-rate-limitの挙動をアプリケーションの特定のニーズに合わせて調整することができます。

handlerのカスタマイズ

express-rate-limithandlerをカスタマイズすることで、Rate Limitに達した際の挙動を変更することができます。
以下に、カスタマイズの例をいくつか示します。

リダイレクト

クライアントを特定のURLにリダイレクトさせたい場合は、res.redirect()を使用します。

handler: (req, res) => {
  res.redirect('/some/path');
},

カスタムHTMLページを送信

特定のHTMLページを表示したい場合、res.sendFile()を使用して静的ファイルを送信できます。

handler: (req, res) => {
  res.sendFile(path.join(__dirname, 'path/to/your/rate-limit-page.html'));
},

レスポンスヘッダーを設定

追加のレスポンスヘッダーを設定して、Rate Limit情報を提供したい場合、res.set()を使用します。

handler: (req, res) => {
  res.set({
    'Retry-After': 3600, // 秒単位でクライアントに再試行を伝える
    'X-RateLimit-Reset': '時間のタイムスタンプ', // Rate Limitがリセットされる予定時刻
  }).status(429).json({
    message: "Too many requests. Please try again later."
  });
},

ロギングや通知の実装

Rate Limitに達したことをロギングするか、システム管理者に通知するなど、バックエンドで何らかのアクションを実行したい場合は、handler内でそれらの処理を実装します。

handler: (req, res) => {
  console.log(`Rate limit exceeded for ${req.ip}`);
  // ここでメール送信や外部APIを呼び出して通知する等の処理を行う
  res.status(429).json({
    message: "Rate limit exceeded. Please try again later."
  });
},

異なるレスポンス形式の提供

クライアントによって期待されるレスポンス形式が異なる場合(例えば、APIエンドポイントではJSONを、ブラウザではHTMLページを提供する)、リクエストのヘッダーやパラメーターに基づいて適切なレスポンスを動的に生成します。

handler: (req, res) => {
  if (req.accepts('html')) {
    res.sendFile(path.join(__dirname, 'path/to/limit-page.html'));
  } else {
    res.status(429).json({ message: "Too many requests. Please try again later." });
  }
},

これらのカスタマイズを通じて、express-rate-limitの挙動をアプリケーションのニーズに合わせて柔軟に調整することが可能です。

storeのカスタマイズ

storeオプションを使用すると、リクエストのカウントを格納する方法をカスタマイズできます。
デフォルトではMemoryStoreが使用されますが、永続的なストアや他のカスタムストアを使用することで、複数のサーバーやプロセス間でRate Limitの状態を共有するなど、より高度なシナリオに対応できます。

MemoryStoreを使用する例

import rateLimit, { MemoryStore } from "express-rate-limit";

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  limit: 1, // 15分間に1リクエストまで許可
  message: "Too many requests from this IP, please try again after 15 minutes",
  statusCode: 429,
  standardHeaders: true,
  legacyHeaders: false,
  store: new MemoryStore(), // MemoryStoreを明示的に使用
});

Redisを使用する例

express-rate-limit自体はRedisを直接サポートしていませんが、サードパーティのライブラリを使ってRedisを使用することができます。
以下は、rate-limit-redisを使用した例です。

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');

// Redisクライアントを作成(設定は環境に合わせて調整してください)
const redisClient = redis.createClient({
  host: 'localhost',
  port: 6379,
  // その他のRedis設定
});

const apiLimiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
  }),
  windowMs: 15 * 60 * 1000, // 15分
  max: 1, // 15分間に1リクエストまで許可
  message: "Too many requests from this IP, please try again after 15 minutes",
  statusCode: 429,
  standardHeaders: true,
  legacyHeaders: false,
});

これにより、アプリケーションのRate Limitの状態がRedisに格納され、複数のプロセスやサーバー間で共有されるようになります。
これは、スケーラブルなアプリケーションや、複数のインスタンスで負荷を分散している場合に特に有用です。

まとめ

express-rate-limitは、ExpressアプリケーションにRate Limitを簡単に追加し、アプリケーションの安定性を保つための効果的なツールです。
また、オプションにより、多様なニーズに対応することができます。

間違っている箇所や疑問に思ったことがあれば、ぜひコメントをください!
最後まで読んでいただき、ありがとうございました。

詳細なコードは、GitHubのリポジトリで公開しています。
興味のある方は是非チェックしてみてください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?