Middleware — это слой между вашим приложением и запросами/ответами, который позволяет выполнять какую-либо работу до того, как запрос попадёт в эндпоинт, и после, перед отправкой ответа клиенту.
Словно вышибалы на вечеринке, они проверяют входящих гостей (запросы) и помогают управлять ситуацией (обрабатывают ответы или ошибки).
Middleware в FastAPI выполняет следующие задачи:
- Интерцепция входящих запросов и обработка их (например, добавление заголовков).
- Интерцепция исходящих ответов или ошибок, которые уже были сгенерированы приложением.
- Возможность кастомной обработки ошибок, логирования и уведомления разработчика.
FastAPI полностью поддерживает асинхронные middleware. Это значит, что они отлично вписываются в концепцию асинхронного программирования (привет async и await!).
Создание middleware для обработки ошибок
Давайте перейдем от теории к практике. Начнем с создания простого middleware, которое будет перехватывать ошибки и заменять стандартные ответы на кастомные.
Для начала создадим middleware, которое будет логировать все ошибки и выдавать клиенту аккуратный ответ:
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
import logging
# Создаем приложение FastAPI
app = FastAPI()
# Настраиваем логирование
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Создаем кастомный middleware
class ErrorHandlingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
# Передача запроса дальше по цепочке
response = await call_next(request)
return response
except Exception as e:
# Логируем ошибку
logger.error(f"Произошла ошибка: {str(e)}")
# Возвращаем кастомный ответ пользователю
return JSONResponse(
status_code=500,
content={"message": "Произошла внутренняя ошибка. Пожалуйста, повторите запрос позже."}
)
# Добавляем middleware в приложение
app.add_middleware(ErrorHandlingMiddleware)
# Пример эндпоинта
@app.get("/")
async def read_root():
raise ValueError("Упс, что-то пошло не так!")
Что здесь происходит?
BaseHTTPMiddleware— это базовый класс, который мы используем для создания своего middleware.- Метод
dispatch(это как "контроллер" в middleware) перехватывает запросы и ловит исключения. - Если где-то в приложении будет ошибка, мы её логируем и возвращаем ответ с кодом 500 (Internal Server Error) в формате JSON.
Теперь, если вы вызовете эндпоинт /, вместо страшной трассировки стека вы увидите понятное сообщение об ошибке.
Добавление уникальных данных в ответы об ошибках
Персонализированные ошибки — это, конечно, круто. Давайте сделаем так, чтобы в каждом ответе была уникальная ID ошибки, по которому можно отследить проблему.
import uuid
from fastapi.responses import JSONResponse
class ErrorHandlingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
response = await call_next(request)
return response
except Exception as e:
# Генерируем уникальный ID для ошибки
error_id = str(uuid.uuid4())
logger.error(f"[Error ID: {error_id}] Произошла ошибка: {str(e)}")
return JSONResponse(
status_code=500,
content={
"message": "Произошла внутренняя ошибка. Пожалуйста, обратитесь в поддержку.",
"error_id": error_id,
}
)
Теперь, если ошибка произойдёт, пользователи получат уникальный error_id, который можно использовать для поиска проблемы в логах. Это особенно удобно в больших проектах.
Middleware для различия типов ошибок
Настало время немного магии. Ваше приложение может выбрасывать разные типы ошибок: например, пользовательские (ошибка бизнес-логики) или серверные. Давайте настроим middleware так, чтобы оно возвращало разные коды статуса в зависимости от типа ошибки.
from fastapi.exceptions import HTTPException
class ErrorHandlingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
response = await call_next(request)
return response
except HTTPException as http_exc:
# Ловим ошибки HTTP (например, 404)
return JSONResponse(
status_code=http_exc.status_code,
content={"message": http_exc.detail}
)
except ValueError as val_err:
# Ловим ошибки бизнес-логики
logger.error(f"Бизнес-ошибка: {str(val_err)}")
return JSONResponse(
status_code=400,
content={"message": "Ошибка бизнес-логики: " + str(val_err)}
)
except Exception as e:
# Логируем остальные ошибки
logger.error(f"Неизвестная ошибка: {str(e)}")
return JSONResponse(
status_code=500,
content={"message": "Что-то пошло не так. Мы работаем над этим!"}
)
Что тут интересного?
- Мы обрабатываем ошибки
HTTPException(например, 404 Not Found) отдельно, чтобы возвращать их как есть. ValueErrorполучает свой кастомный ответ (например, для бизнес-логики).- Все остальные ошибки обрабатываются как неизвестные.
Асинхронный контекст в middleware
Теперь давайте немного усложним задачу. Что, если нужно обратиться к стороннему API или базе данных внутри вашего middleware? Такой сценарий тоже вполне рабочий.
class AsyncLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
response = await call_next(request)
return response
except Exception as e:
# Пример асинхронного действия, например, логирование в сторонний сервис
await send_error_to_external_service(str(e))
return JSONResponse(
status_code=500,
content={"message": "Ошибка была зафиксирована и будет исправлена."}
)
# Пример асинхронной функции для отправки ошибки
async def send_error_to_external_service(error_message: str):
# Представим вызов стороннего API
await asyncio.sleep(1) # Симуляция задержки
logger.info(f"Ошибка отправлена во внешнюю систему: {error_message}")
Особенности и подводные камни
- Порядок middleware имеет значение: если у вас несколько middleware, их порядок регистрации влияет на то, как обрабатываются запросы.
- Не злоупотребляйте middleware: пытайтесь не делать в нем слишком сложных операций, так как это может замедлить обработку запросов.
- Логирование может стоить дорого: если логи пишутся слишком часто, это может повлиять на производительность в высоконагруженных системах.
Практическое применение
Использование middleware для обработки ошибок особенно важно в масштабируемых и высоконагруженных приложениях. В реальных проектах middleware помогает:
- Логировать ошибки централизованно в таких системах, как Sentry или Elastic Stack.
- Обрабатывать ошибки API, сохраняя их в базе данных для дальнейшего анализа.
- Отправлять уведомления разработчикам о критических ошибках.
Теперь вы готовы использовать middleware для обработки ошибок в своих проектах. В следующей лекции мы расширим ваши знания об асинхронной обработке ошибок. Следите за перехватами! 😉
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ