|
|
@ -82,15 +82,15 @@ RL_INDEX_API_BLOCKED_IPS = 'rl:index:api_blocked_ips' |
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- |
|
|
# --------------------------------------------------------------------------- |
|
|
# API quota keys — per-tenant HASH, one key per period. |
|
|
# API quota keys — per-tenant HASH, one key per period. |
|
|
# Structure: HASH key = quota:{ns}:daily:{date} field = {ip} value = counter |
|
|
# Structure: HASH key = api_quota:{ns}:daily:{date} field = {ip} value = counter |
|
|
# HASH key = quota:{ns}:weekly:{week} field = {ip} value = counter |
|
|
# HASH key = api_quota:{ns}:weekly:{week} field = {ip} value = counter |
|
|
# Weekly key uses ISO week notation (yyyy-Www) — unambiguous, Monday-anchored. |
|
|
# Weekly key uses ISO week notation (yyyy-Www) — unambiguous, Monday-anchored. |
|
|
# TTL set once on hash creation (Lua TTL guard); resets are implicit in the |
|
|
# TTL set once on hash creation (Lua TTL guard); resets are implicit in the |
|
|
# date/week embedded in the key name. Fields are IPs; no per-field TTL. |
|
|
# date/week embedded in the key name. Fields are IPs; no per-field TTL. |
|
|
# Memory: ~63 bytes/field vs ~148 bytes for per-IP STRING keys (~57% saving). |
|
|
# Memory: ~63 bytes/field vs ~148 bytes for per-IP STRING keys (~57% saving). |
|
|
# --------------------------------------------------------------------------- |
|
|
# --------------------------------------------------------------------------- |
|
|
QUOTA_DAILY_HASH = 'quota:{ns}:daily:{date}' |
|
|
API_QUOTA_DAILY_HASH = 'api_quota:{ns}:daily:{date}' |
|
|
QUOTA_WEEKLY_HASH = 'quota:{ns}:weekly:{week}' |
|
|
API_QUOTA_WEEKLY_HASH = 'api_quota:{ns}:weekly:{week}' |
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- |
|
|
# --------------------------------------------------------------------------- |
|
|
# Bot UA fragments |
|
|
# Bot UA fragments |
|
|
@ -607,13 +607,13 @@ class RateLimitMiddleware: |
|
|
week_str = f'{iso[0]}-W{iso[1]:02d}' |
|
|
week_str = f'{iso[0]}-W{iso[1]:02d}' |
|
|
|
|
|
|
|
|
ip = get_client_ip(request) |
|
|
ip = get_client_ip(request) |
|
|
d_hash = QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=date_str) |
|
|
api_d_hash = API_QUOTA_DAILY_HASH.format(ns=_NAMESPACE, date=date_str) |
|
|
w_hash = QUOTA_WEEKLY_HASH.format(ns=_NAMESPACE, week=week_str) |
|
|
api_w_hash = API_QUOTA_WEEKLY_HASH.format(ns=_NAMESPACE, week=week_str) |
|
|
|
|
|
|
|
|
try: |
|
|
try: |
|
|
if _hincrby_with_ttl(d_hash, ip, 86400) > self.api_quota_daily: |
|
|
if _hincrby_with_ttl(api_d_hash, ip, 86400) > self.api_quota_daily: |
|
|
return 'daily' |
|
|
return 'daily' |
|
|
if _hincrby_with_ttl(w_hash, ip, 7 * 86400) > self.api_quota_weekly: |
|
|
if _hincrby_with_ttl(api_w_hash, ip, 7 * 86400) > self.api_quota_weekly: |
|
|
return 'weekly' |
|
|
return 'weekly' |
|
|
except Exception: |
|
|
except Exception: |
|
|
pass # fail open — quota not enforced when Redis unavailable |
|
|
pass # fail open — quota not enforced when Redis unavailable |
|
|
|