Создание 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
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 выполняется в следующем порядке:
- process_request - от первого к последнему
- View - обработка запроса
- process_response - от последнего к первому
- 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")
Лучшие практики
- Используй MiddlewareMixin для совместимости с разными версиями Django
- Обрабатывай исключения в middleware
- Не делай middleware слишком тяжелым - это влияет на производительность
- Тестируй middleware отдельно
- Документируй назначение и поведение 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.