Какие ключевые методы необходимо реализовать для создания middleware в Python?

Ответ

Для создания middleware в Python, как правило, реализуется класс с обязательными методами __init__ и __call__. Конкретная сигнатура этих методов зависит от спецификации, которой следует фреймворк (например, ASGI или WSGI).

Основной принцип middleware — это "обертка" над следующим обработчиком запроса, позволяющая выполнить код до и после его отработки.

Пример для Django (на основе WSGI)

В Django middleware инициализируется один раз и затем вызывается для каждого запроса.

class SimpleMiddleware:
    def __init__(self, get_response):
        # get_response — это следующий middleware в цепочке или само view
        self.get_response = get_response

    def __call__(self, request):
        # Код, который выполняется до вызова view
        print("Before request")

        response = self.get_response(request)

        # Код, который выполняется после вызова view
        print("After request")
        return response

Пример для FastAPI/Starlette (на основе ASGI)

ASGI-middleware работает с асинхронными вызовами и оперирует более низкоуровневыми объектами scope, receive, send.

from starlette.types import ASGIApp, Scope, Receive, Send

class CustomMiddleware:
    def __init__(self, app: ASGIApp):
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        # Код до обработки запроса
        if scope["type"] == "http":
            # Можно модифицировать scope или заголовки
            pass

        await self.app(scope, receive, send)

        # Код после обработки запроса (сложнее, т.к. ответ уже может быть отправлен)

Ключевые методы и их роли:

  • __init__(self, get_response / app): Конструктор. Вызывается один раз при запуске сервера для настройки middleware. Принимает следующий обработчик в цепочке.
  • __call__(self, request / scope, receive, send): Основной метод. Вызывается для каждого запроса. Именно здесь реализуется логика обработки.

Ответ 18+ 🔞

А, ну вот, слушай, смотри, блядь. Middleware, сука, это как такой охранник на входе в клуб, понимаешь? Каждый, кто хочет зайти к тебе на view — сначала через него проходит, ёпта.

Вот смотри, в Python это обычно класс, да? И там два главных метода, без которых нихуя не работает. __init__ и __call__. Всё, как у людей, блядь.

__init__ — это типа сборка перед боем. Вызывается один раз, когда сервер запускается, и всё, пиздец. Ему говорят: «Вот, держи следующего пацана в цепочке (это get_response или app), он после тебя будет работать». И он его себе в карман кладёт, блядь, и ждёт.

А __call__ — это уже сам процесс, сука. Это когда конкретный клиент пришёл, и ты его начинаешь обрабатывать. Ты можешь с ним что-то сделать ДО того, как передашь его дальше, и ПОСЛЕ того, как он оттуда вернётся. Вот и вся магия, ёбана!

Теперь смотри, как это в разных бандах выглядит.

Для Django (этот старый, но проверенный WSGI-пахан)

Тут всё просто, как три копейки. Middleware получает get_response — это следующий мужик в очереди (или само view). А потом в __call__ приходит request, и ты его можешь пощупать.

class SimpleMiddleware:
    def __init__(self, get_response):
        # Запоминаем следующего в цепочке, типа "за мной, сука, очередь!"
        self.get_response = get_response

    def __call__(self, request):
        # Тут ты делаешь что хочешь ДО того, как запрос уйдёт дальше
        print("Щас запрос пойдёт, блядь!")

        # А вот тут ты его передаёшь дальше по конвейеру
        response = self.get_response(request)

        # А тут ты можешь накостылить что-то ПОСЛЕ того, как ответ пришёл
        print("Всё, запрос отгремел, можно и выпить!")
        return response

Для FastAPI или Starlette (эти новые, асинхронные ASGI-шники)

Тут уже посложнее, потому что всё асинхронное, и работают они с какими-то scope, receive, send. Выглядит страшновато, но принцип тот же, блядь.

from starlette.types import ASGIApp, Scope, Receive, Send

class CustomMiddleware:
    def __init__(self, app: ASGIApp):
        # Опять же, цепляем следующее приложение
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        # Scope — это типа контекст запроса: кто, откуда, что хочет
        # Тут можно его почитать или даже подправить, если ты такой хитрый
        if scope["type"] == "http":
            # Ну, например, проверить заголовки, блядь
            pass

        # Передаём управление дальше
        await self.app(scope, receive, send)

        # А вот тут, сука, осторожно! Ответ уже мог улететь клиенту,
        # так что сильно потанцевать не получится. Но кое-что сделать можно.

Итог, ёпта:

  • __init__ — настройка один раз при старте. Получаем ссылку на следующего.
  • __call__ — работа для каждого запроса. Делаем что-то до, пускаем запрос дальше, делаем что-то после.

Вот и вся философия, блядь. Ничего сложного, просто обёртка над основным обработчиком, как бутерброд вокруг колбасы. Главное — не переборщить с логикой, а то запрос нахуй так и не дойдёт, застрянет в твоих дебажных принтах, ебать!