レートリミット(SlowAPI)¶
SlowAPI は、レートリミットをエンドポイントごとに @limiter デコレーターで設定できるライブラリ。
後々 Redis を使うので、 Redis のセットアップも済ませておいた方がいい。
- インストール
サンプルコード¶
/ へのアクセスを 1 分間に 5 回までに制限する。
# main.py
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(
RateLimitExceeded,
_rate_limit_exceeded_handler, # type: ignore
)
@app.get("/")
@limiter.limit("5/minute")
async def read_root(request: Request):
return {"message": "Rate is not exceeded."}
実験1¶
Uvicorn をワーカー数 1 で起動してレートリミットが効いているかを確認する。
| 項目 | 値 |
|---|---|
| --workers | 1 |
| FastAPI サーバー | 192.168.0.88 |
| クライアント | 192.168.0.89, 192.168.0.90 の 2 つ |
curl で http://192.168.0.88:8000/ にリクエストを送る。1 秒間に 1 回くらいでテキトーに連打する。
Uvicornログ
$ uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1
INFO: Started server process [1659]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 192.168.0.89:62501 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:62502 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:62503 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:62504 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:62505 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:62506 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:62507 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:62508 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62364 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62365 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62366 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62367 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62368 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62369 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62370 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62371 - "GET / HTTP/1.1" 429 Too Many Requests
### 1分間待つ ###
INFO: 192.168.0.89:62756 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:62757 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:61310 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:61311 - "GET / HTTP/1.1" 200 OK
^CINFO: Shutting down
- 5 回目まではどっちのクライアントも OK だけど、 6 回目以降は Too Many Requests になってる。 IP アドレスごとに制限できてる。
- 1 分経過したら、どっちのクライアントも OK に戻る。制限される時間も OK。
実験2¶
ワーカー数を 4 に増やすとどうなるかを確認する。
| 項目 | 値 |
|---|---|
| --workers | 4 |
| FastAPI サーバー | 192.168.0.88 |
| クライアント | 192.168.0.89 の 1 つ |
curl を 1 秒間に 1 回くらいでテキトーに連打する。
Uvicornログ
$ uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started parent process [1707]
INFO: Started server process [1713]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [1710]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [1712]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [1711]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 192.168.0.89:63523 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63524 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63525 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63526 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63527 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63528 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63529 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63530 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63531 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63532 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63533 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63534 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63535 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63536 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63537 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63538 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63539 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63540 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63541 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63542 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63543 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63544 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63545 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63546 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63547 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63548 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63549 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63550 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63551 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63552 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63553 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63554 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63555 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63556 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63557 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63558 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63559 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:63560 - "GET / HTTP/1.1" 429 Too Many Requests
(中略)
INFO: 192.168.0.89:63092 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63093 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63094 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63095 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63096 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63097 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63098 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63099 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63100 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63101 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63102 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63103 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63104 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63105 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63106 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63107 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:63108 - "GET / HTTP/1.1" 429 Too Many Requests
^CINFO: Shutting down
- 5 回ではレートリミットがかからなくて、 20 回(ワーカー数 × 5)を使い切る感じになってる。
- どのワーカーがリクエストを受け付けるかによって、制限がかかるときもあれば、まだ大丈夫なときもある。
- ちゃんと FastAPI がロードバランスをやってる証拠でもあるか。
- 複数ワーカーで FastAPI を起動しつつレートリミットも安定させたいとなると、他の方法と組み合わせないとダメ。
- Redis が使えそう: cf. Use Redis as backend for the limiter
サンプルコード変更: Redis を使うように¶
Redis のセットアップを完了させてから、 Limiter に storage_uri を渡す。
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(
key_func=get_remote_address,
storage_uri="redis://localhost:6379",
)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(
RateLimitExceeded,
_rate_limit_exceeded_handler, # type: ignore
)
@app.get("/")
@limiter.limit("5/minute")
async def read_root(request: Request):
return {"message": "Rate is not exceeded."}
実験3¶
ストレージを Redis にして、ワーカー数 4 でもレートリミットが安定するかを確認する。
| 項目 | 値 |
|---|---|
| --workers | 4 |
| FastAPI サーバー | 192.168.0.88 |
| クライアント | 192.168.0.89, 192.168.0.90 の 2 つ |
| ストレージ | Redis |
Uvicornログ
$ uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started parent process [14292]
INFO: Started server process [14294]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [14295]
INFO: Started server process [14296]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [14297]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 192.168.0.89:61726 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:61727 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:61728 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:61729 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:61730 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.89:61731 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61732 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61733 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61734 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61735 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62739 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62740 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62741 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62742 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62743 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62744 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62745 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62746 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62747 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:62748 - "GET / HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:62493 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.90:62420 - "GET / HTTP/1.1" 200 OK
^CINFO: Shutting down
- IP 単位で制限できてるし、 1 分間に 5 回までっていうのもちゃんと効いてる。複数ワーカーでも Redis でカウンターを共有できてる。
Redis の中身も確認していく。
起動直後は KEYS に何もない。
クライアント 192.168.0.89 から 1 回 curl した後。まだ制限されないけど KEY に値が入って TTL も減り始める。カウンターに 1 が入る。
127.0.0.1:6379> KEYS *
1) "LIMITS:LIMITER/192.168.0.89///5/1/minute"
127.0.0.1:6379> TTL "LIMITS:LIMITER/192.168.0.89///5/1/minute"
(integer) 48
127.0.0.1:6379> GET "LIMITS:LIMITER/192.168.0.89///5/1/minute"
"1"
192.168.0.89 から 6 回連続で curl した後。制限された状態で、カウンターの値自体は 5 を超えてる( 10 にでも 1000 にでもなる)。
127.0.0.1:6379> KEYS *
1) "LIMITS:LIMITER/192.168.0.89///5/1/minute"
127.0.0.1:6379> TTL "LIMITS:LIMITER/192.168.0.89///5/1/minute"
(integer) 51
127.0.0.1:6379> GET "LIMITS:LIMITER/192.168.0.89///5/1/minute"
"6"
TTL の 1 分以上が経過した後。 KEY は削除済み( TTL が -2 )になって、 カウンターもない( nil )。
127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379> TTL "LIMITS:LIMITER/192.168.0.89///5/1/minute"
(integer) -2
127.0.0.1:6379> GET "LIMITS:LIMITER/192.168.0.89///5/1/minute"
(nil)
192.168.0.89 と 192.168.0.90 それぞれから 1 回ずつ curl した後。 IP アドレスごとに KEY が作られてる。
127.0.0.1:6379> KEYS *
1) "LIMITS:LIMITER/192.168.0.89///5/1/minute"
2) "LIMITS:LIMITER/192.168.0.90///5/1/minute"
サンプルコード変更: パスパラメータごとに制限する API(/hello)を追加¶
パスパラメータに入ってる name を取り出す get_path_param_name 関数を定義して、 key_func=get_path_param_name として @limiter デコレーターに書く。これで同じ name では 1 分間に 3 回までしかリクエストできなくなる。
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
def get_path_param_name(request: Request) -> str:
name = request.path_params.get("name", "unknown")
return f"name:{name}"
limiter = Limiter(
key_func=get_remote_address,
storage_uri="redis://localhost:6379",
)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(
RateLimitExceeded,
_rate_limit_exceeded_handler, # type: ignore
)
@app.get("/")
@limiter.limit("5/minute")
async def read_root(request: Request):
return {"message": "Rate is not exceeded."}
@app.get("/hello/{name}")
@limiter.limit("3/minute", key_func=get_path_param_name)
async def hello(request: Request, name: str):
return {"message": f"Hello, {name}"}
実験4¶
パスパラメータの name ごとに 3 回までの制限がちゃんとかかるかを確認する。
| 項目 | 値 |
|---|---|
| --workers | 4 |
| FastAPI サーバー | 192.168.0.88 |
| クライアント | 192.168.0.89 の 1 つ |
| ストレージ | Redis |
curl で 1 分間以内に Uvicorn ログにある全てのリクエストを送った。
Uvicornログ
$ uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started parent process [16488]
INFO: Started server process [16490]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [16491]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [16492]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [16493]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 192.168.0.89:60401 - "GET /hello/alice HTTP/1.1" 200 OK
INFO: 192.168.0.89:60402 - "GET /hello/alice HTTP/1.1" 200 OK
INFO: 192.168.0.89:60403 - "GET /hello/alice HTTP/1.1" 200 OK
INFO: 192.168.0.89:60404 - "GET /hello/alice HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:60405 - "GET /hello/alice HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:60406 - "GET /hello/alice2 HTTP/1.1" 200 OK
INFO: 192.168.0.89:60407 - "GET /hello/alice2 HTTP/1.1" 200 OK
INFO: 192.168.0.89:60408 - "GET /hello/alice2 HTTP/1.1" 200 OK
INFO: 192.168.0.89:60409 - "GET /hello/alice2 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:60410 - "GET /hello/alice2 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:60411 - "GET /hello/alice3 HTTP/1.1" 200 OK
INFO: 192.168.0.89:60412 - "GET /hello/alice3 HTTP/1.1" 200 OK
INFO: 192.168.0.89:60413 - "GET /hello/alice3 HTTP/1.1" 200 OK
INFO: 192.168.0.89:60414 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:60415 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
^CINFO: Shutting down
nameごとに 3 回までなら OK が返ってきて、それ以上は Too Many Requests。
Redis の中身も確認する。起動直後は KEY が何もない。
name=alice, alice2, alice3 でリクエストして制限がかかった後。
127.0.0.1:6379> KEYS *
1) "LIMITS:LIMITER/name:alice3//hello/alice3/3/1/minute"
2) "LIMITS:LIMITER/name:alice//hello/alice/3/1/minute"
3) "LIMITS:LIMITER/name:alice2//hello/alice2/3/1/minute"
127.0.0.1:6379> GET "LIMITS:LIMITER/name:alice//hello/alice/3/1/minute"
"5"
127.0.0.1:6379> GET "LIMITS:LIMITER/name:alice2//hello/alice2/3/1/minute"
"5"
127.0.0.1:6379> GET "LIMITS:LIMITER/name:alice3//hello/alice3/3/1/minute"
"5"
サンプルコード変更: /hello に IP アドレスごとの制限も追加¶
@limiter デコレーターを 2 つ書く。 key_func を指定しない場合は、 Limiter 定義の key_func=get_remote_address が効く。
パスパラメータが入ってると、 IP アドレスごとに回数制限をしても name が変わるたびに URL が変わって /hello 全体に対する制限ができないので、 shared_limit を scope 付きで使う。
Info
例えば、パスパラメーターとしてメールアドレスを受け取るエンドポイントがあったと考える。
IPアドレスごとの制限もしておかないと 実験 4 の結果から、攻撃者が大量のメールアドレスのリストを使ってアカウントが存在するかどうかを調べられる。
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
def get_path_param_name(request: Request) -> str:
name = request.path_params.get("name", "unknown")
return f"name:{name}"
limiter = Limiter(
key_func=get_remote_address,
storage_uri="redis://localhost:6379",
)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(
RateLimitExceeded,
_rate_limit_exceeded_handler, # type: ignore
)
@app.get("/")
@limiter.limit("5/minute")
async def read_root(request: Request):
return {"message": "Rate is not exceeded."}
@app.get("/hello/{name}")
@limiter.shared_limit("5/minute", scope="hello") # scope は名前をつけてるだけ。何でもいい
@limiter.limit("3/minute", key_func=get_path_param_name)
async def hello(request: Request, name: str):
return {"message": f"Hello, {name}"}
実験5¶
name ごとの 3 回制限と、 /hello 全体への IP ごと 5 回制限( shared_limit )が両立するかを確認する。
| 項目 | 値 |
|---|---|
| --workers | 4 |
| FastAPI サーバー | 192.168.0.88 |
| クライアント | 192.168.0.89, 192.168.0.90 の 2 つ |
| ストレージ | Redis |
curl で 1 分間以内に Uvicorn ログにある全てのリクエストを送った。。
Uvicornログ
$ uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started parent process [16838]
INFO: Started server process [16840]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [16842]
INFO: Started server process [16841]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Started server process [16843]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 192.168.0.89:61606 - "GET /hello/alice HTTP/1.1" 200 OK
INFO: 192.168.0.89:61607 - "GET /hello/alice HTTP/1.1" 200 OK
INFO: 192.168.0.89:61608 - "GET /hello/alice HTTP/1.1" 200 OK
INFO: 192.168.0.89:61609 - "GET /hello/alice HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61610 - "GET /hello/alice HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61611 - "GET /hello/alice HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61612 - "GET /hello/alice2 HTTP/1.1" 200 OK
INFO: 192.168.0.89:61613 - "GET /hello/alice2 HTTP/1.1" 200 OK
INFO: 192.168.0.89:61614 - "GET /hello/alice2 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61615 - "GET /hello/alice2 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61616 - "GET /hello/alice2 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61617 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61618 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61619 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61620 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.89:61621 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:61030 - "GET /hello/alice HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:61031 - "GET /hello/alice2 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:61032 - "GET /hello/alice3 HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:61033 - "GET /hello/tiangolo HTTP/1.1" 200 OK
INFO: 192.168.0.90:61034 - "GET /hello/tiangolo HTTP/1.1" 200 OK
INFO: 192.168.0.90:61035 - "GET /hello/tiangolo HTTP/1.1" 200 OK
INFO: 192.168.0.90:61036 - "GET /hello/tiangolo HTTP/1.1" 429 Too Many Requests
INFO: 192.168.0.90:61037 - "GET /hello/tiangolo HTTP/1.1" 429 Too Many Requests
^CINFO: Shutting down
- 192.168.0.89,
/hello/aliceは 3 回目まで OK。 - 192.168.0.89,
/hello/alice2は 2 回目までしか OK にならず、 3 回目以降は Too Many Requests。 - 192.168.0.89,
/hello/alice3は 1 回目から Too Many Requests。 - 同じパスパラメータなら 3 回まで、パスパラメータを抜いた URL(
/hello)に対するアクセスは 5 回までっていう制限ができてる。 - 192.168.0.90,
/hello/alice,/hello/alice2,/hello/alice3はすべて 1 回目から Too Many Requests(別 IP だけど/hello全体の 5 回をすでに使い切ってる)。 - 192.168.0.90,
/hello/tiangoloは 3 回目まで OK、 4 回目以降は Too Many Requests。
Redis の中身も確認する。起動直後は KEY が何もない。
name=alice, alice2, alice3 でリクエストして制限がかかった後。 /hello へのリクエストは、パスパラメータ抜きで KEY を作ってる。
127.0.0.1:6379> KEYS *
1) "LIMITS:LIMITER/name:alice2//hello/alice2/3/1/minute"
2) "LIMITS:LIMITER/192.168.0.89/hello/5/1/minute"
3) "LIMITS:LIMITER/name:alice//hello/alice/3/1/minute"
4) "LIMITS:LIMITER/name:alice3//hello/alice3/3/1/minute"
5) "LIMITS:LIMITER/192.168.0.90/hello/5/1/minute"
6) "LIMITS:LIMITER/name:tiangolo//hello/tiangolo/3/1/minute"
失敗例
shared_limit を使わずに limit( @limiter.limit("5/minute") )を使うと、パスパラメータを含めた URL に対して IP アドレスごとに KEY を作ってしまう。これだと name が変わるたびに別カウントになって、 /hello 全体への制限にならない。
127.0.0.1:6379> KEYS *
1) "LIMITS:LIMITER/192.168.0.89//hello/alice3/5/1/minute"
2) "LIMITS:LIMITER/name:alice2//hello/alice2/3/1/minute"
3) "LIMITS:LIMITER/192.168.0.89//hello/alice2/5/1/minute"
4) "LIMITS:LIMITER/name:alice3//hello/alice3/3/1/minute"
5) "LIMITS:LIMITER/name:alice//hello/alice/3/1/minute"
6) "LIMITS:LIMITER/192.168.0.89//hello/alice/5/1/minute"
メモ¶
KEY の名前は、 SlowAPI が依存してる limits ライブラリの key_for 関数で作られてる。
cf. limits/limits.py の key_for