jimble

レートリミット#

**ルートに宣言します。**書いたところにだけかかります。

// 1本だけ
post("/api/login", handler)
	.attribute(RateLimit.KEY, RateLimit.perIp(5, Duration.ofMinutes(1)));

// このブロック全部
path("/api", () -> {
	rateLimit(RateLimit.perIp(60, Duration.ofMinutes(1)));

	get("/items", handler);
	get("/items/{id}", handler);
});

before と同じで書いたブロックに付きます(パスのノードではありません)。 内側に別のものを書けば内側が勝ち、ルートが属性で持っていればそれが勝ちます。

数え方#

トークンバケットです。duration かけて limit 個まで戻ります。 短いバーストは通り、続けて叩くと止まります。

補足

固定の窓(「1分あたり N 回」)にしなかったのは、 窓の境目で一瞬 2N 回通ってしまうためです。

数える単位#

RateLimit.perIp(60, Duration.ofMinutes(1));                       // 送信元ごと
RateLimit.of(context -> context.session().id(), 10, Duration.ofMinutes(1));   // ログインごと

落とし穴

**perIpserver.trust_proxy を見ます。**プロキシの後ろに置いているのに false のままだと、全員がロードバランサの IP として1つに数えられますサーバー設定)。

社内からのアクセスなど、数えたくないものは外せます。

RateLimit.perIp(60, Duration.ofMinutes(1))
	.exclude(context -> context.request().proxyAddress().startsWith("10."));

止めたときに返るもの#

ステータス 429
Retry-After 待つ秒数(切り上げ。最低 1)
X-RateLimit-Limit / X-RateLimit-Remaining 上限と残り

**before より前で止めます。**認証や DB を触らせないためです。

置き場#

rate_limit {
	store   = "memory"   # memory | redis | db
	enabled = true
}
台をまたぐか 向き
memory(既定) またがない 1台のとき
redis またぐ 複数台に並べるとき
db またぐ Redis が無くて、回数が少ないとき

注意

memory のまま複数台に並べると、全体では台数ぶん通ります。 3台なら 3 倍です。並べるなら redis にしてください。

補足

redis は Lua で数えます。「残り」と「最後に足した時刻」を 一緒に読み書きしないと数え落ちるためです。 dbSELECT ... FOR UPDATE なので、高い頻度には向きません (ログインの試行回数のような用途向け)。テーブルは初めて使うときに作ります。

落とし穴

数えられなかったときは通します。Redis が落ちているだけで サイト全体が 429 になるほうが困るからです。 ただし黙っては通しません流量制限を数えられませんでした(通します) がログに出ます)。