JavaRush /Курсы /Модуль 4: FastAPI /Как использовать middleware для обработки ошибок

Как использовать middleware для обработки ошибок

Модуль 4: FastAPI
20 уровень , 5 лекция
Открыта

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 для обработки ошибок в своих проектах. В следующей лекции мы расширим ваши знания об асинхронной обработке ошибок. Следите за перехватами! 😉

1
Задача
Модуль 4: FastAPI, 20 уровень, 5 лекция
Недоступна
Простое Middleware для обработки ошибок
Простое Middleware для обработки ошибок
1
Задача
Модуль 4: FastAPI, 20 уровень, 5 лекция
Недоступна
Кастомная обработка business-ошибок
Кастомная обработка business-ошибок
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ