Что такое middleware и как оно работает в веб-фреймворках?
Комментарии (2)
Ллддд
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое middleware и как оно работает в веб-фреймворках?
Основная идея
Middleware — это функция или класс, который обрабатывает запросы и ответы между клиентом и вашим приложением. Это промежуточный слой, который может перехватывать, модифицировать или блокировать запросы/ответы.
Это паттерн декоратора и цепочки ответственности, используемый во всех серьёзных веб-фреймворках:
- Django
- Flask
- FastAPI
- aiohttp
- Express.js (JavaScript)
Как это работает: конвейер
Запрос ────────────────────────────────────
↓
Middleware 1 (request) ← Может модифицировать
↓
Middleware 2 (request)
↓
Middleware 3 (request)
↓
[Ваша бизнес-логика / View]
↓
Middleware 3 (response) ← Может модифицировать
↓
Middleware 2 (response)
↓
Middleware 1 (response)
↓
───────────────────────────────────────→ Ответ
Важно: middleware выполняются в обратном порядке при обработке ответа!
Django: Middleware
В Django это классы с методами __init__, process_request, process_response:
class LoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# ДО обработки запроса
print(f"Входящий запрос: {request.method} {request.path}")
# Обработка запроса (вызов остального конвейера)
response = self.get_response(request)
# ПОСЛЕ обработки запроса
print(f"Ответ: {response.status_code}")
return response
# В settings.py
MIDDLEWARE = [
'myapp.middleware.LoggingMiddleware',
# ... другой middleware
]
FastAPI: Middleware (современный подход)
FastAPI использует асинхронный middleware:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import time
app = FastAPI()
# Вариант 1: Декоратор
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# ДО обработки
start_time = time.time()
# Обработка
response = await call_next(request)
# ПОСЛЕ обработки
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
# Вариант 2: Класс
from starlette.middleware.base import BaseHTTPMiddleware
class CustomMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# ДО
print(f"Запрос: {request.method} {request.url.path}")
# Обработка
response = await call_next(request)
# ПОСЛЕ
response.headers["X-Custom"] = "modified"
return response
app.add_middleware(CustomMiddleware)
Flask: Middleware через декораторы
Фласк использует более простой подход с before_request и after_request:
from flask import Flask, request, g
import time
app = Flask(__name__)
# Выполняется ДО каждого запроса
@app.before_request
def before_request():
g.start_time = time.time()
print(f"Запрос начался: {request.method} {request.path}")
# Выполняется ПОСЛЕ каждого запроса (если не было ошибки)
@app.after_request
def after_request(response):
elapsed = time.time() - g.start_time
response.headers["X-Process-Time"] = str(elapsed)
return response
# Выполняется на ошибках
@app.teardown_request
def teardown_request(exception=None):
if exception:
print(f"Ошибка: {exception}")
Примеры полезного middleware
1. Аутентификация
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
# Проверяем наличие токена
token = request.headers.get("Authorization")
if not token and "/auth/" not in request.url.path:
return JSONResponse(
status_code=401,
content={"detail": "Требуется авторизация"}
)
# Сохраняем пользователя в request
if token:
request.state.user = parse_token(token)
# Продолжаем обработку
response = await call_next(request)
return response
2. Логирование
import logging
from fastapi import Request
logger = logging.getLogger(__name__)
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
# Логируем входящий запрос
logger.info(f"{request.method} {request.url.path}")
response = await call_next(request)
# Логируем ответ
logger.info(f"Status: {response.status_code}")
return response
3. CORS (Cross-Origin Resource Sharing)
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["GET", "POST"],
allow_headers=["Content-Type"],
)
4. Проверка прав доступа
from fastapi import Request, HTTPException
@app.middleware("http")
async def admin_middleware(request: Request, call_next):
# Проверяем доступ к /admin
if request.url.path.startswith("/admin/"):
user = request.state.user
if not user or not user.is_admin:
return JSONResponse(
status_code=403,
content={"detail": "Доступ запрещён"}
)
return await call_next(request)
5. Кэширование
from fastapi import Request
from fastapi.responses import Response
cache = {}
@app.middleware("http")
async def cache_middleware(request: Request, call_next):
# Кэшируем только GET запросы
cache_key = f"{request.method}:{request.url.path}"
if request.method == "GET" and cache_key in cache:
return cache[cache_key]
response = await call_next(request)
if request.method == "GET":
cache[cache_key] = response
return response
Важный момент: порядок выполнения
@app.middleware("http")
async def middleware_1(request: Request, call_next):
print("M1: до")
response = await call_next(request)
print("M1: после")
return response
@app.middleware("http")
async def middleware_2(request: Request, call_next):
print("M2: до")
response = await call_next(request)
print("M2: после")
return response
# Выполнится:
# M1: до
# M2: до
# [View обработка]
# M2: после
# M1: после
Важно: middleware добавляются в обратном порядке!
Асинхронность в middleware
Современные фреймворки поддерживают асинхронный middleware:
@app.middleware("http")
async def async_db_middleware(request: Request, call_next):
# Можем использовать await
async with get_db() as db:
request.state.db = db
response = await call_next(request)
return response
Обработка ошибок в middleware
from fastapi import Request
from fastapi.responses import JSONResponse
@app.middleware("http")
async def error_middleware(request: Request, call_next):
try:
response = await call_next(request)
except ValueError as e:
return JSONResponse(
status_code=400,
content={"detail": str(e)}
)
except Exception as e:
return JSONResponse(
status_code=500,
content={"detail": "Internal server error"}
)
return response
Сравнение фреймворков
| Фреймворк | Тип | Синтаксис | Асинхронность |
|---|---|---|---|
| Django | Класс | __call__ | Нет (исторически) |
| FastAPI | Функция/Класс | @app.middleware | Да |
| Flask | Декоратор | @app.before_request | Ограниченная |
| aiohttp | Функция | @web.middleware | Да |
Лучшие практики
-
Держите middleware простым
# Плохо: сложная логика в middleware @app.middleware("http") async def bad_middleware(request, call_next): # 100 строк логики... # Хорошо: выносим в отдельные функции async def authenticate(request): pass @app.middleware("http") async def good_middleware(request, call_next): await authenticate(request) return await call_next(request) -
Не изменяйте request/response внутри
# Опасно: может сломать другой middleware request.method = "GET" # Хорошо: используем request.state для данных request.state.user = user -
Обрабатывайте исключения
@app.middleware("http") async def safe_middleware(request, call_next): try: return await call_next(request) except Exception as e: logger.exception(e) return JSONResponse(status_code=500) -
Используйте контекстные менеджеры
@app.middleware("http") async def context_middleware(request, call_next): async with get_transaction() as txn: request.state.transaction = txn response = await call_next(request) return response
Заключение
Middleware — это мощный инструмент для обработки сквозных задач (authentication, logging, error handling, CORS). Понимание порядка выполнения и асинхронности критично для разработки надёжных веб-приложений. Помните: middleware выполняются в цепочке, где каждый может модифицировать запрос или ответ до передачи дальше.
Ддддюююю