Создание middleware в Django: полное руководство

Middleware в Django - это мощный механизм для обработки запросов и ответов на уровне всего приложения. Он позволяет выполнять код до и после обработки каждого запроса, что идеально подходит для логирования, аутентификации, CORS и других задач.

Что такое Django middleware?

Middleware - это класс, который обрабатывает запросы и ответы в процессе их прохождения через Django. Каждый middleware может:

  • Обрабатывать запрос до того, как он дойдет до view
  • Обрабатывать ответ после того, как view его сгенерирует
  • Модифицировать запрос или ответ
  • Прерывать обработку запроса

Структура middleware

Каждый middleware должен реализовывать метод __call__:

 1class SimpleMiddleware:
 2  def __init__(self, get_response):
 3      self.get_response = get_response
 4
 5  def __call__(self, request):
 6      # Код, выполняемый до view
 7      print(f"Обрабатывается запрос: {request.path}")
 8
 9      # Передаем управление следующему middleware или view
10      response = self.get_response(request)
11
12      # Код, выполняемый после view
13      print(f"Получен ответ: {response.status_code}")
14
15      return response

Практические примеры middleware

1. Middleware для логирования запросов

 1import time
 2import logging
 3from django.utils.deprecation import MiddlewareMixin
 4
 5logger = logging.getLogger(__name__)
 6
 7class RequestLoggingMiddleware(MiddlewareMixin):
 8    def __init__(self, get_response):
 9        self.get_response = get_response
10
11    def __call__(self, request):
12        # Засекаем время начала обработки
13        start_time = time.time()
14
15        # Логируем информацию о запросе
16        logger.info(f"Начало обработки запроса: {request.method} {request.path}")
17
18        # Обрабатываем запрос
19        response = self.get_response(request)
20
21        # Вычисляем время обработки
22        duration = time.time() - start_time
23
24        # Логируем результат
25        logger.info(
26            f"Запрос {request.method} {request.path} обработан за {duration:.2f}с, "
27            f"статус: {response.status_code}"
28        )
29
30        return response

2. Middleware для аутентификации по API ключу

 1from django.contrib.auth.models import User
 2  from django.http import JsonResponse
 3  from django.utils.deprecation import MiddlewareMixin
 4
 5  class APIKeyAuthMiddleware(MiddlewareMixin):
 6      def __init__(self, get_response):
 7          self.get_response = get_response
 8
 9      def __call__(self, request):
10          # Проверяем API ключ только для API запросов
11          if request.path.startswith('/api/'):
12              api_key = request.headers.get('X-API-Key')
13
14              if not api_key:
15                  return JsonResponse(
16                      {'error': 'API ключ не предоставлен'},
17                      status=401
18                  )
19
20              try:
21                  # Ищем пользователя по API ключу
22                  user = User.objects.get(profile__api_key=api_key)
23                  request.user = user
24              except User.DoesNotExist:
25                  return JsonResponse(
26                      {'error': 'Неверный API ключ'},
27                      status=401
28                  )
29
30          return self.get_response(request)

3. Middleware для CORS

 1from django.utils.deprecation import MiddlewareMixin
 2
 3  class CORSMiddleware(MiddlewareMixin):
 4      def __init__(self, get_response):
 5          self.get_response = get_response
 6
 7      def __call__(self, request):
 8          response = self.get_response(request)
 9
10          # Добавляем CORS заголовки
11          response["Access-Control-Allow-Origin"] = "*"
12          response["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
13          response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
14
15          return response
16
17      def process_view(self, request, view_func, view_args, view_kwargs):
18          # Обрабатываем preflight запросы
19          if request.method == "OPTIONS":
20              response = HttpResponse()
21              response["Access-Control-Allow-Origin"] = "*"
22              response["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
23              response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
24              return response
25
26          return None

4. Middleware для кэширования

 1from django.core.cache import cache
 2  from django.utils.deprecation import MiddlewareMixin
 3  import hashlib
 4
 5class CacheMiddleware(MiddlewareMixin):
 6    def __init__(self, get_response):
 7        self.get_response = get_response
 8
 9    def __call__(self, request):
10        # Генерируем ключ кэша на основе URL и параметров
11        cache_key = self._generate_cache_key(request)
12
13        # Проверяем кэш для GET запросов
14        if request.method == 'GET':
15            cached_response = cache.get(cache_key)
16            if cached_response:
17                return cached_response
18
19        # Обрабатываем запрос
20        response = self.get_response(request)
21
22        # Кэшируем успешные GET ответы
23        if request.method == 'GET' and response.status_code == 200:
24            cache.set(cache_key, response, timeout=300)  # 5 минут
25
26        return response
27
28    def _generate_cache_key(self, request):
29        """Генерирует уникальный ключ для кэша"""
30        url = request.get_full_path()
31        user_id = getattr(request.user, 'id', 'anonymous')
32        return hashlib.md5(f"{url}:{user_id}".encode()).hexdigest()

5. Middleware для rate limiting

 1from django.core.cache import cache
 2from django.http import HttpResponseTooManyRequests
 3from django.utils.deprecation import MiddlewareMixin
 4import time
 5
 6class RateLimitMiddleware(MiddlewareMixin):
 7    def __init__(self, get_response):
 8        self.get_response = get_response
 9        self.rate_limit = 100  # запросов в минуту
10        self.window = 60  # секунд
11
12    def __call__(self, request):
13        # Получаем IP адрес
14        ip = self._get_client_ip(request)
15
16        # Проверяем лимит запросов
17        if not self._check_rate_limit(ip):
18            return HttpResponseTooManyRequests(
19                "Превышен лимит запросов. Попробуйте позже."
20            )
21
22        return self.get_response(request)
23
24    def _get_client_ip(self, request):
25        """Получает реальный IP адрес клиента"""
26        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
27        if x_forwarded_for:
28            ip = x_forwarded_for.split(',')[0]
29        else:
30            ip = request.META.get('REMOTE_ADDR')
31        return ip
32
33    def _check_rate_limit(self, ip):
34        """Проверяет лимит запросов для IP"""
35        current_time = int(time.time())
36        window_start = current_time - self.window
37
38        # Получаем количество запросов в текущем окне
39        key = f"rate_limit:{ip}:{current_time // self.window}"
40        requests_count = cache.get(key, 0)
41
42        if requests_count >= self.rate_limit:
43            return False
44
45        # Увеличиваем счетчик
46        cache.set(key, requests_count + 1, timeout=self.window)
47        return True

Специальные методы middleware

Django middleware может реализовывать дополнительные методы:

process_request

1class CustomMiddleware(MiddlewareMixin):
2    def process_request(self, request):
3        """Выполняется до view, может вернуть response для прерывания"""
4        if request.path.startswith('/admin/') and not request.user.is_staff:
5            return HttpResponseForbidden("Доступ запрещен")
6        return None  # Продолжаем обработку

process_response

1class CustomMiddleware(MiddlewareMixin):
2    def process_response(self, request, response):
3        """Выполняется после view, всегда должен вернуть response"""
4        # Добавляем заголовок
5        response['X-Custom-Header'] = 'Custom Value'
6        return response

process_exception

1class ExceptionLoggingMiddleware(MiddlewareMixin):
2    def process_exception(self, request, exception):
3        """Выполняется при возникновении исключения в view"""
4        logger.error(
5            f"Исключение в {request.path}: {str(exception)}",
6            exc_info=True
7        )
8        return None  # Django обработает исключение стандартно

Регистрация middleware

Добавь middleware в настройки проекта:

 1# settings.py
 2MIDDLEWARE = [
 3    'django.middleware.security.SecurityMiddleware',
 4    'django.contrib.sessions.middleware.SessionMiddleware',
 5    'django.middleware.common.CommonMiddleware',
 6    'django.middleware.csrf.CsrfViewMiddleware',
 7    'django.contrib.auth.middleware.AuthenticationMiddleware',
 8    'django.contrib.messages.middleware.MessageMiddleware',
 9    'django.middleware.clickjacking.XFrameOptionsMiddleware',
10    'your_app.middleware.CustomMiddleware',  # Твой middleware
11]

Порядок выполнения middleware

Middleware выполняется в следующем порядке:

  1. process_request - от первого к последнему
  2. View - обработка запроса
  3. process_response - от последнего к первому
  4. process_exception - при ошибках

Тестирование middleware

Вот пример теста для middleware:

 1from django.test import TestCase, RequestFactory
 2from django.http import HttpResponse
 3from .middleware import CustomMiddleware
 4
 5class MiddlewareTestCase(TestCase):
 6    def setUp(self):
 7        self.factory = RequestFactory()
 8        self.middleware = CustomMiddleware(lambda req: HttpResponse("OK"))
 9
10    def test_middleware_processes_request(self):
11        """Тестирует обработку запроса middleware"""
12        request = self.factory.get('/test/')
13        response = self.middleware(request)
14
15        self.assertEqual(response.status_code, 200)
16        self.assertEqual(response.content.decode(), "OK")

Лучшие практики

  1. Используй MiddlewareMixin для совместимости с разными версиями Django
  2. Обрабатывай исключения в middleware
  3. Не делай middleware слишком тяжелым - это влияет на производительность
  4. Тестируй middleware отдельно
  5. Документируй назначение и поведение middleware

Отладка middleware

Для отладки middleware используй логирование:

 1import logging
 2from django.utils.deprecation import MiddlewareMixin
 3
 4logger = logging.getLogger(__name__)
 5
 6class DebugMiddleware(MiddlewareMixin):
 7    def __call__(self, request):
 8        logger.debug(f"Middleware: начало обработки {request.path}")
 9
10        response = self.get_response(request)
11
12        logger.debug(f"Middleware: завершение обработки {request.path}")
13        return response

FAQ

Q: Когда использовать middleware?
A: Для обработки запросов/ответов на уровне всего приложения: логирование, аутентификация, CORS, кэширование, rate limiting.

Q: Можно ли использовать middleware для бизнес-логики?
A: Лучше избегать сложной бизнес-логики в middleware. Используй их для технических задач.

Q: Какой порядок выполнения middleware?
A: process_request выполняется от первого к последнему, process_response - наоборот.

Q: Можно ли прервать выполнение запроса в middleware?
A: Да, если process_request возвращает HttpResponse, запрос не дойдет до view.

Q: Как тестировать middleware?
A: Используй RequestFactory для создания тестовых запросов и проверяй результат.

Заключение

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