Нить безопасное хранение в Джанго - PullRequest
1 голос
/ 13 июня 2019

Я настраиваю обработчик электронной почты администратора Django и добавляю в него некоторую пакетную защиту, чтобы при возникновении нескольких ошибок в течение одной минуты отправлялось только одно сообщение об ошибке.

    def burst_protection(self, lag=60):
        """
        :param lag:
        :return:
        """
        current_time = int(time.time())
        global timestamp

        if current_time - timestamp > lag:
            timestamp = current_time
            enable_burst_protection = False
        else:
            enable_burst_protection = True

        return enable_burst_protection

Изначально я реализовал метку времени как переменную класса, но это не защищает от посылок сообщений в нашей производственной среде, поскольку я предполагаю, что на сервере имеется несколько потоков или процессов, обращающихся и одновременно записывающих метку времени , Есть ли поток и обработать безопасный способ сохранить значение метки времени в Python / Django?

Я слышал, что это возможно, сохраняя значение метки времени в базе данных, но я бы предпочел избежать доступа к базе данных для этого.

Ответы [ 2 ]

1 голос
/ 14 июня 2019

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
1 голос
/ 13 июня 2019

Есть метод, но:

Чтобы обеспечить безопасность потоков, для каждого потока будет возвращаться отдельный экземпляр серверной части кэша.

Вы можете использовать кеширование, а если вы не хотите использовать базу данных, вы можете создать кэш-память в памяти.

https://docs.djangoproject.com/en/2.2/topics/cache/

Посмотрите на Кэширование в локальной памяти раздел

Пример (из документов):

settings.py

    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

Затем вы можете использовать низкоуровневое кэширование, тот же раздел страницы API низкоуровневого кэширования

views.py - или где бы ни находился ваш пакет

from django.core.cache import caches
cache1 = caches['unique-snowflake'] # we get our cache handle
cache1.set('my_key', time())
...
cache1.get('my_key')

...