Зарегистрируйтесь, чтобы продолжить обучение

Middlewares Python: Веб-разработка (Flask)

При разработке веб-приложений разработчики сталкиваются с задачами, которые нужно решать на каждом этапе обработки запроса. Например, если у нас есть несколько маршрутов, которые требуют аутентификации, или если нам нужно логировать все входящие запросы, то реализация этой логики в каждом маршруте может привести к дублированию кода и усложнению поддержки приложения. Это также увеличивает вероятность ошибок, так как любой измененный маршрут необходимо будет обновить в нескольких местах. Использование мидлвар позволяет решить эти проблемы.

Мидлвара - это функции, которые обрабатывают запросы и ответы в веб-приложениях. Они выполняются между получением запроса от клиента и отправкой ответа. Они могут выполнять различные задачи, такие как проверка прав доступа, логирование или преобразование данных в ответе

В этом уроке мы рассмотрим, как создавать и использовать мидлвары в Flask, а также изучим примеры их применения

Оборачивание

Прежде чем перейти к изучению непосредственно мидлвар, разберем их принцип действия на примере обычных функций.

Представим, что у нас есть функция, которая прибавляет 5 к аргументу:

def add_five(num):
    return num + 5

Наша задача расширить возможности этой функции, при этом не меняя саму функцию. Например, мы хотим прибавлять 5 к удвоенному значению аргумента. Чтобы решить эту задачу, мы можем написать новую функцию, которая будет внутри себя использовать уже существующую функцию:

def double_add_five(x):
    return add_five(x * 2)

Точно так же мы можем расширить и вторую функцию:

def calculate(x):
    return double_add_five(x - 10) - 3

В общем случае расширение функции выглядит так:

def next_method(args):
    # preprocessing
    result = prev_method(updated_args)
    # afterprocessing
    return new_result

Чтобы расширить поведение функции, нужно создать новую функцию, которая будет использовать исходную. Главное условие — интерфейсы, то есть сигнатуры, обеих функций должны совпадать. Тогда мы можем заменить в коде вызов одной функции на вызов другой, обернутой, и код продолжит работать без изменений, поскольку интерфейс остался прежним. Код, использующий новую функцию, не заметит, что он обернут и его не потребуется переписывать. Этот подход также называется декорированием.

По похожей идее работают мидлвары. Они расширяют функционал обработчиков запросов, оборачивая их в подобные методы. При этом оригинальный обработчик остается неизменным.

Использование мидлвар

Чтобы использовать мидлвары в Flask, не нужно ничего устанавливать, фреймворк имеет встроенную поддержку мидлвар. Для добавления мидлвар используются декораторы @before_request или @after_request. Декоратор @before_request используется для указания кода, который должен быть выполнен до запроса, а @after_request — после.

Рассмотрим пример кода с подключением мидлвары:

from flask import Flask, request

app = Flask(__name__)


@app.before_request
def log_path():
    print(f"Request path: {request.path}")


@app.route("/")
def home():
    return "Hello from middleware!"


@app.after_request
def log_response(response):
    print("Response has been sent")
    return response

В приведенном примере подключена мидлвара, которая для каждого входящего запроса выводит в консоль сообщение с указанием пути. Также добавлена мидлвара, которая выполняется после обработки запроса и выводит в консоль сообщение, информируя о том, что ответ был успешно отправлен клиенту. Во Flask в мидлваре @after_request обязательно нужно вернуть объект response.

Каждый раз, когда мы используем @before_request или @after_request, очередная мидлвара добавляется в общую цепочку. Каждый запрос, отправляемый на обработку в приложение, проходит через всю цепочку этих мидлвар

pipline

Flask по умолчанию выполняет все before мидлвары по порядку, как они заданы в коде. Но если какая-то из них возвращает значение, то на ней закончится вызов, и вернется response.

Если же ни одна из before мидлвар не совершила return, то выполнение дойдет до обработчика маршрута.

Затем, в конце, выполнится after мидлвара.

from flask import Flask, request

app = Flask(__name__)


@app.before_request
def log_path():
    print(f"Request path: {request.path}")


@app.after_request
def add_custom_header(response):
    response.headers["X-Custom-Header"] = "value"
    return response


@app.route("/")
def home():
    return "Welcome to Hexlet!"

В примере выше мы подключили мидлвары, одна из которых выводит в консоль сообщение, а другая добавляет кастомный заголовок X-Custom-Header в каждый ответ.

Запустим сервер и сделаем запрос:

curl --head localhost:5000

HTTP/1.1 200 OK
Server: Werkzeug/3.0.6 Python/3.13.0rc2
Content-Type: text/html; charset=utf-8
Content-Length: 18
X-Custom-Header: value # Наш установленный заголовок
Connection: close

При этом в консоли, где запущено приложение, мы можем увидеть вывод:

Request path: /

Обработчики

Самое интересное в мидлварах, что обработчики конкретных маршрутов — это тоже мидлвары. Но в отличие от мидлвар, которые выполняются для всех запросов, обработчики привязаны к конкретному маршруту.

@app.before_request
def middleware():
    print("1. First middleware")


@app.route("/")
def home():
    print("2. Route handler")
    return "Hello"


@app.after_request
def middleware3(response):
    print("3. After middleware")
    return response

Терминальная мидлвара

Далеко не всегда мы хотим двигаться вглубь по всей цепочке добавленных мидлвар. Например, хотим создать мидлвару для проверки наличия определенного параметра в запросе, не меняя при этом сам обработчик. Если параметр отсутствует, мы можем сразу отправить ответ с ошибкой и пропустить выполнение остальных мидлвар и обработчиков. В таком случае нам нужно сделать return в мидлваре. После возврата движение по цепочке прекратится, все оставшиеся мидлвары пропускаются и происходит отправка ответа:

from flask import Flask, request

app = Flask(__name__)


@app.before_request
def check_id():
    if request.endpoint == "resource":
        id = request.args.get("id")
        if not id:
            # в случае запроса на /resouce сработает условие мидлвары
            return 'Bad Request: Missing "id" parameter', 400
    return None  # иначе возвращаем None, чтобы продолжить цепочку


@app.before_request
def log_path():
    print(f"Request path: {request.path}")


@app.route("/")
def home():
    return "Hello from Hexlet"


@app.route("/resource")
def resource():
    id = request.args.get("id")
    return f"Resource with id: {id}"
curl localhost:5000/resource

HTTP/1.1 400 BAD REQUEST
Connection: close
Content-Length: 35
Content-Type: text/html; charset=utf-8
Server: Werkzeug/3.0.6 Python/3.13.0rc2

Bad Request: Missing "id" parameter

У такого поведения, когда есть цепочка функций и любая из них в процессе обработки может принять решение остановки цепочки и возврата ответа, есть имя. Такие цепочки называют chain responsibility, и это тоже паттерн

Помимо этого существует мидлвары выполнямые после отправки ответа - teardown_request(). Эта мидлвара выполнится даже в случае, если во время выполнения программы сгенерируется и не будет обработано исключение.

from flask import Flask

app = Flask(__name__)


@app.teardown_request
def run_always(exception):
    print("This will always run")


@app.route("/")
def home():
    raise Exception("Something went wrong")
curl -Is localhost:5000/
HTTP/1.1 500 INTERNAL SERVER ERROR
Server: Werkzeug/3.0.6 Python/3.13.0rc2
Date: Fri, 21 Feb 2025 14:40:19 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 13849
Connection: close

В консоли, где запущено приложение, мы можем увидеть, что сообщение из мидлвары все равно выведется, и лишь затем будет стектрейс ошибки:

This will always run

Traceback (most recent call last):
  line 1498, in __call__
    return self.wsgi_app(environ, start_response)

Дополнительные материалы

  1. before_request
  2. after_request

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff