Какие объекты обрабатывает middleware?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие объекты обрабатывает middleware
Middleware — это промежуточный обработчик, который работает между клиентом и приложением. Он перехватывает и трансформирует различные объекты на разных этапах жизненного цикла запроса.
Основные объекты в HTTP middleware
1. Request объект
Объект HTTP запроса от клиента.
from fastapi import Request, FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Middleware получает Request объект
print(f"Method: {request.method}")
print(f"URL: {request.url}")
print(f"Headers: {request.headers}")
print(f"Query params: {request.query_params}")
# Можно читать body (осторожно! иногда stream-based)
body = await request.body()
print(f"Body: {body}")
# Передаём дальше
response = await call_next(request)
return response
Атрибуты Request:
method— GET, POST, PUT, DELETE и т.п.url— полный URLpath— путь без базового URLquery_params— параметры строки запросаheaders— HTTP заголовкиbody()— тело запроса (асинхронно)json()— парсированный JSONform()— данные формыcookies— cookiesclient— IP адрес и порт клиентаuser— аутентифицированный пользователь (если есть)scope— ASGI scope dict
2. Response объект
Объект HTTP ответа, который возвращает обработчик.
from starlette.responses import Response, JSONResponse
class ResponseModifyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response: Response = await call_next(request)
# Middleware может модифицировать Response
print(f"Status code: {response.status_code}")
print(f"Headers: {response.headers}")
# Добавить заголовок
response.headers["X-Custom-Header"] = "middleware-added"
# Изменить status code
if response.status_code == 404:
response.status_code = 200
return response
Атрибуты Response:
status_code— HTTP код (200, 404, 500)headers— ответные заголовкиbody— тело ответа (bytes)media_type— MIME тип
3. Cookies
Cookies хранятся в Request и Response.
class CookieMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Читать cookies из запроса
session_id = request.cookies.get("session_id")
print(f"Session: {session_id}")
response = await call_next(request)
# Установить cookie в ответ
response.set_cookie(
key="tracking",
value="tracked",
max_age=3600,
httponly=True # Важно для безопасности!
)
return response
4. Headers
HTTP заголовки в запросе и ответе.
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Прочитать заголовки запроса
auth_token = request.headers.get("Authorization")
user_agent = request.headers.get("User-Agent")
response = await call_next(request)
# Добавить заголовки безопасности в ответ
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Strict-Transport-Security"] = "max-age=31536000"
response.headers["Content-Security-Policy"] = "default-src 'self'"
return response
5. ASGI Scope и Receive/Send
Низкоуровневые ASGI объекты (для специальных случаев).
from starlette.types import ASGIApp, Message, Receive, Send, Scope
class ASGIMiddleware:
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send):
# scope — информация о подключении
print(f"Type: {scope['type']}") # 'http' или 'websocket'
print(f"Method: {scope['method']}")
print(f"Path: {scope['path']}")
print(f"Headers: {scope['headers']}")
# receive — асинхронный канал получения сообщений
# send — асинхронный канал отправки сообщений
await self.app(scope, receive, send)
ASGI цикл:
clиент → receive() → middleware ← send() → клиент
6. Stream (для больших тел запроса)
Для файлов и потоков данных.
class UploadMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Для файловых upload-ов body обычно stream
if request.method == "POST" and "multipart/form-data" in request.headers.get("content-type", ""):
# Можно читать body частями
async for chunk in request.stream():
print(f"Chunk size: {len(chunk)}")
response = await call_next(request)
return response
7. State (для передачи данных между middleware)
Объект для хранения state между middleware и обработчиком.
from fastapi import Request, FastAPI
import time
app = FastAPI()
class PerformanceMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Сохранить start time в state
request.state.start_time = time.time()
response = await call_next(request)
# Получить данные из state
duration = time.time() - request.state.start_time
response.headers["X-Process-Time"] = str(duration)
return response
@app.get("/")
def read_root(request: Request):
# Обработчик может читать state из middleware
start_time = request.state.start_time
return {"message": "Hello", "start_time": start_time}
Типы middleware в разных фреймворках
FastAPI / Starlette
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.gzip import GZIPMiddleware
app = FastAPI()
# Встроенный middleware для CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# GZIP compression
app.add_middleware(GZIPMiddleware, minimum_size=1000)
# Custom middleware
class CustomMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Обрабатываю Request
response = await call_next(request)
# Обрабатываю Response
return response
app.add_middleware(CustomMiddleware)
Django
# 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',
'myapp.middleware.CustomMiddleware',
]
# myapp/middleware.py
class CustomMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request): # Request перед обработчиком
# Обработка request
print(f"Request: {request.method} {request.path}")
response = self.get_response(request) # Вызов обработчика
# Обработка response
response['X-Custom-Header'] = 'value'
return response
Flask
from flask import Flask, request, after_this_request
app = Flask(__name__)
@app.before_request
def before_request():
# Обработка перед обработчиком
print(f"Request: {request.method} {request.path}")
request.custom_data = {"start_time": time.time()}
@app.after_request
def after_request(response):
# Обработка после обработчика
response.headers['X-Custom'] = 'value'
return response
Celery / Message Queue middleware
from celery import Celery
from celery.signals import before_task_publish, task_prerun, task_postrun
app = Celery()
@before_task_publish.connect
def before_publish(sender=None, body=None, **kwargs):
# Middleware перед отправкой задачи
print(f"Publishing task: {sender}")
@task_prerun.connect
def task_prerun_handler(sender=None, task_id=None, **kwargs):
# Middleware перед запуском задачи
print(f"Task {task_id} starting")
@task_postrun.connect
def task_postrun_handler(sender=None, task_id=None, **kwargs):
# Middleware после завершения задачи
print(f"Task {task_id} completed")
Практический пример: Auth middleware
from fastapi import FastAPI, Request, HTTPException, status
from starlette.middleware.base import BaseHTTPMiddleware
import jwt
app = FastAPI()
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Пропускаем публичные endpoints
public_paths = {"/health", "/docs", "/login"}
if request.url.path in public_paths:
return await call_next(request)
# Получить токен из заголовка
auth_header = request.headers.get("Authorization")
if not auth_header:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
try:
# Парсить и проверить токен
scheme, token = auth_header.split()
if scheme.lower() != "bearer":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
payload = jwt.decode(token, "secret-key", algorithms=["HS256"])
user_id = payload.get("user_id")
# Сохранить пользователя в state
request.state.user_id = user_id
except (jwt.InvalidTokenError, ValueError):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
response = await call_next(request)
return response
app.add_middleware(AuthMiddleware)
@app.get("/profile")
def get_profile(request: Request):
user_id = request.state.user_id # Получено из middleware
return {"user_id": user_id, "name": "John"}
Сравнение объектов
| Объект | Тип | Используется для | Пример |
|---|---|---|---|
| Request | HTTP | Чтение данных клиента | request.method, request.body() |
| Response | HTTP | Изменение ответа | response.status_code, response.headers |
| Headers | HTTP | Security, CORS, Caching | X-Custom, Authorization |
| Cookies | HTTP | Session, Tracking | session_id, csrf_token |
| ASGI Scope | ASGI | Низкоуровневая обработка | scope['type'], scope['method'] |
| Stream | Data | Большие файлы | request.stream() |
| State | Context | Передача между middleware | request.state.user_id |
| Message (Task) | Queue | Фоновые задачи | Celery, RabbitMQ |
Порядок выполнения middleware
request
↓
[Middleware 1] → request phase
↓
[Middleware 2] → request phase
↓
[Handler]
↓
[Middleware 2] → response phase
↓
[Middleware 1] → response phase
↓
response
Порядок добавления важен! Первый добавленный middleware = последний в цепи.
app.add_middleware(AuthMiddleware) # Выполнится 2-й
app.add_middleware(LoggingMiddleware) # Выполнится 1-й
Best practices для middleware
class GoodMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# ✅ DO: Оберни в try-except
try:
response = await call_next(request)
except Exception as e:
# Логируй ошибку
logger.exception(f"Error in handler: {e}")
# Верни ошибку
return JSONResponse(
status_code=500,
content={"error": "Internal Server Error"}
)
# ✅ DO: Не блокируй (async)
response.headers["X-Process-Time"] = str(time.time())
# ✅ DO: Не читай body без необходимости
# (body может быть большой, и сумеш прочитать только раз)
return response
class BadMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# ❌ DON'T: Читаю body, и потом обработчик не может его прочитать
body = await request.body()
# body stream уже исчерпан!
# ❌ DON'T: Синхронный код (блокирует)
time.sleep(1) # Медленно!
# ❌ DON'T: Не ловлю ошибки
response = await call_next(request)
return response
Заключение
Middleware обрабатывает:
- Request — метод, URL, заголовки, body, cookies
- Response — status code, headers, body
- State — контекст для передачи данных
- ASGI-level objects — для специальных случаев
Мидлвэйр — это мощный инструмент для cross-cutting concerns: аутентификация, логирование, кэширование, rate limiting и т.п.
В production я всегда использую middleware для:
- Authentication & Authorization
- Logging & Monitoring
- Error Handling
- CORS & Security Headers
- Request/Response transformation