При разработке веб-приложений разработчики сталкиваются с задачами, которые нужно решать на каждом этапе обработки запроса. Например, если у нас есть несколько маршрутов, которые требуют аутентификации, или если нам нужно логировать все входящие запросы, то реализация этой логики в каждом маршруте может привести к дублированию кода и усложнению поддержки приложения. Это также увеличивает вероятность ошибок, так как любой измененный маршрут необходимо будет обновить в нескольких местах. Использование мидлвар позволяет решить эти проблемы.
Мидлвара - это функции, которые обрабатывают запросы и ответы в веб-приложениях. Они выполняются между получением запроса от клиента и отправкой ответа. Они могут выполнять различные задачи, такие как проверка прав доступа, логирование или преобразование данных в ответе
В этом уроке мы рассмотрим, как создавать и использовать мидлвары в 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
, очередная мидлвара добавляется в общую цепочку. Каждый запрос, отправляемый на обработку в приложение, проходит через всю цепочку этих мидлвар
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)
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.