Skip to content

Redis. Middleware. Cache

Pronich edited this page Nov 6, 2021 · 1 revision

Чем полезен Middleware в Django

Middleware - это надстройка в Django проекте, которая отрабатывает перед вью-функциями. Другими словами - это надстройка над основным приложением. В Django уже есть встроенные Middleware, которые решают определённые задачи. Например, SessionMiddleware отвечает за сессии пользователя, когда тот подключается к приложению. Middleware выполняются в той очередности, в которой они записаны в файле settings.py:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

    'shop.middleware.CacheMethodsMiddleware',
]

Чтобы подключить свой модуль - достаточно указать путь к нему.

Middleware в Django

class CacheMethodsMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response

Для создания Middleware лучше всего использовать Class, но также можно и функцию. Class Middleware имеет 2 основных метода:

  1. __init__(self, get_response) - отвечает за инициализаю Middleware. Данные метод запускается 1 раз в момент запуска приложения
  2. __call__(self, request) - выполняется каждый раз, когда идет обращение к View-функциям.

response = self.get_response(request) отвечает за вызов следующего в порядке Middleware, либо если это последний - View функции.

Пример работы Middleware

Создадим 2 Middleware и добавим функции print() до и после response = self.get_response(request).

class FirstMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        print('First call middleware')
        response = self.get_response(request)
        print('First end middleware')
        return response

class SecondMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        print('Second call middleware')
        response = self.get_response(request)
        print('Second end middleware')
        return response

Если мы запустим наше приложение и сделаем вызов View функции (которая в процессе обработки также выводим что-то в терминал), то в терминале мы увидим следующий порядок:

First call middleware
Second call middleware
View result
Second end middleware
First end middleware

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

Дополнения к Middleware

Кроме того, чтобы можно менять логику до и после response = self.get_response(request), также можно добавлять различные обработчики - например, обработчики ошибок. Для этого нужно создать один из методов, представленный в документации к Django Middleware.

class CacheMethodsMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response

    def process_exception(request, exception):
        # Тут можно сделать дополнительную логику обработки ошибок
        return None

Соответственно, если наше View кинуло Exception, то можно сделать обработчик и трекинг этой ошибки. Именно тут можно реализовать Alerts, Logs, и их вывод. Метод возвращает либо None, либо HttpResponse.

Middleware with Redis & Cache

Redis

Для подключения Redis в качестве системы хранения информации, необходимо в settings.py добавить:

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': f'redis://{env("REDIS_HOST")}:{env("REDIS_PORT")}/',
        'TIMEOUT': env('REDIS_EXP_TIME'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

В нашем случае TIMEOUT задается через переменные окружения. Также можно его задавать через Config-file, а также использовать DEFAULT_TIMEOUT. Данные параметр отвечает за то, сколько информация в формате ключ-значение будет храниться в Redis. Этот параметр можно не указывать, тогда для установки времени хранения необходимо передавать данный параметр в методе set(key, value, exp_time). При этом если не передать это значение - берется DEFAULT_TIMEOUT.

Для работы с Redis используется 2 метода: get(key) и set(key, value).

Middleware для Redis

Описана текущая реализация

class CacheMethodsMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        self.path = request.path_info.lstrip('/').split('/')[2]
        if self.path in cache:
            if cache.get(self.path) is not None:
                resp = json.loads(cache.get(self.path))
                return HttpResponse(resp, status=status.HTTP_200_OK)
        else:
            response = self.get_response(request)
            cache.set(self.path, json.dumps(response.data))
            return response

Что тут происходит:

  1. Идет вызов от клиента к View функции
  2. До перехода на следующий этап мы получаем информацию о пути, по которому к нам идут
  3. Делаем проверку в Redis на наличие пары key-value по ключу, полученному из пути
  4. Если пара есть
    1. Возвращаем value для этого ключа
    2. Переводим это все в Json формат
    3. С помощью Return прекращаем дальнейшую цепочку и возвращаем пользователю ответ
  5. Если пары нет
    1. Продолжаем цепочку до View
    2. Получает ответ от View-функции
    3. Кладем в Redis данные с ключом == пути. В Value записываем данные, преобразованные в JSON-формат (потому что они приходят в формате OrderedDict)
    4. Возвращаем пользователю ответ от View-функции

Clone this wiki locally