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 для обробки помилок у своїх проєктах. У наступній лекції ми розширимо твої знання про асинхронну обробку помилок. Слідкуй за перехопленнями! 😉
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ