Redis довольно хорош для реализаций с ограничением скорости, например ::10000
нам нужен уникальный ключ для каждого пользователя, здесь мы используем либо ключ сеанса, либо ip-адрес пользователя + хеш строки пользовательского агента, но если вы хотите оценить ограничение по всему миру, то возвращая константу (например, * Параметр 1003 *) сделает это:
import hashlib
def _ratelimit_key(servicename, request):
"""Return a key that identifies one visitor uniquely and is durable.
"""
sesskey = request.session.session_key
if sesskey is not None:
unique = sesskey
else:
ip = request.get_host()
ua = request.META.get('HTTP_USER_AGENT', 'no-user-agent')
digest = hashlib.md5(ua).hexdigest()
unique = '%s-%s' % (ip, digest)
return '%s-%s' % (servicename, unique)
тогда ограничитель скорости может быть реализован как декоратор:
import time
from django.conf import settings
from django import http
import redis
def strict_ratelimit(name, seconds=10, message="Too many requests."):
"""Basic rate-limiter, only lets user through 10 seconds after last attempt.
Args:
name: the service to limit (in case several views share a service)
seconds: the length of the quiet period
message: the message to display to the user when rate limited
"""
def decorator(fn):
def wrap(request, *args, **kwargs):
r = redis.Redis()
key = _ratelimit_key(name, request)
if r.exists(key):
r.expire(key, seconds) # refresh timeout
return http.HttpResponse(message, status=409)
r.setex(key, seconds, "nothing")
return fn(request, *args, **kwargs)
return wrap
return decorator
Использование:
@strict_ratelimit('search', seconds=5)
def my_search_view(request):
...
Строгий ограничитель скорости, как правило, не тот, который вам нужен, однако, как правило, вы хотите, чтобы у людей были небольшие всплески (если в промежутке времени их не слишком много).
Алгоритм "дырявого ведра" (google it) делает это (то же использование, что и выше):
def leaky_bucket(name, interval=30, size=3, message="Too many request."):
"""Rate limiter that allows bursts.
Args:
name: the service to limit (several views can share a service)
interval: the timperiod (in seconds)
size: maximum number of activities in a timeperiod
message: message to display to the user when rate limited
"""
def decorator(fn):
def wrap(request, *args, **kwargs):
r = redis.Redis()
key = _ratelimit_key(name, request)
if r.exists(key):
val = r.hgetall(key)
value = float(val['value'])
now = time.time()
# leak the bucket
elapsed = now - float(val['timestamp'])
value -= max(0.0, elapsed / float(interval) * size)
if value + 1 > size:
r.hmset(key, dict(timestamp=now, value=value))
r.expire(key, interval)
return http.HttpResponse(message, status=409)
else:
value += 1.0
r.hmset(key, dict(timestamp=now, value=value))
r.expire(key, interval)
return fn(request, *args, **kwargs)
else:
r.hmset(key, dict(timestamp=time.time(), value=1.0))
r.expire(key, interval)
return fn(request, *args, **kwargs)
return wrap
return decorator