2024-01-30 18:29:59 +01:00
|
|
|
from datetime import datetime
|
|
|
|
from typing import Optional
|
|
|
|
|
2024-02-06 11:55:45 +01:00
|
|
|
import newrelic.agent
|
2024-01-30 18:29:59 +01:00
|
|
|
import redis.exceptions
|
|
|
|
import werkzeug.exceptions
|
|
|
|
from limits.storage import RedisStorage
|
|
|
|
|
2024-02-20 11:13:06 +01:00
|
|
|
from app.log import LOG
|
2024-01-30 18:29:59 +01:00
|
|
|
|
|
|
|
lock_redis: Optional[RedisStorage] = None
|
|
|
|
|
|
|
|
|
|
|
|
def set_redis_concurrent_lock(redis: RedisStorage):
|
|
|
|
global lock_redis
|
|
|
|
lock_redis = redis
|
|
|
|
|
|
|
|
|
|
|
|
def check_bucket_limit(
|
|
|
|
lock_name: Optional[str] = None,
|
|
|
|
max_hits: int = 5,
|
|
|
|
bucket_seconds: int = 3600,
|
|
|
|
):
|
|
|
|
# Calculate current bucket time
|
2024-02-20 11:13:06 +01:00
|
|
|
int_time = int(datetime.utcnow().timestamp())
|
|
|
|
bucket_id = int_time - (int_time % bucket_seconds)
|
2024-01-30 18:29:59 +01:00
|
|
|
bucket_lock_name = f"bl:{lock_name}:{bucket_id}"
|
2024-02-05 14:53:01 +01:00
|
|
|
if not lock_redis:
|
|
|
|
return
|
2024-01-30 18:29:59 +01:00
|
|
|
try:
|
|
|
|
value = lock_redis.incr(bucket_lock_name, bucket_seconds)
|
|
|
|
if value > max_hits:
|
2024-04-08 15:05:51 +02:00
|
|
|
LOG.i(
|
|
|
|
f"Rate limit hit for {lock_name} (bucket id {bucket_id}) -> {value}/{max_hits}"
|
|
|
|
)
|
2024-02-06 11:55:45 +01:00
|
|
|
newrelic.agent.record_custom_event(
|
|
|
|
"BucketRateLimit",
|
|
|
|
{"lock_name": lock_name, "bucket_seconds": bucket_seconds},
|
|
|
|
)
|
2024-01-30 18:29:59 +01:00
|
|
|
raise werkzeug.exceptions.TooManyRequests()
|
2024-02-05 14:53:01 +01:00
|
|
|
except (redis.exceptions.RedisError, AttributeError):
|
2024-02-20 11:13:06 +01:00
|
|
|
LOG.e("Cannot connect to redis")
|