Формирование ответа сервера, декораторы перехвата запроса

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

<заголовок ответа><HTML-документ>

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

  • 200 – все нормально;
  • 404 – страница не найдена;
  • 301 – выполнено перенаправление с другого URL;
  • 401 – доступ запрещен

и так далее. Кроме того, в заголовке в параметре content-type прописывается тип данных:

  • text/html
  • text/plain
  • image/jpeg
  • audio/mp4
  • multipart/form-data

Все это имеет большое значение при разработке сайтов. На этом занятии мы как раз и поговорим о работе с этим заголовком.

Обратите внимание, заголовок ответа сервера не имеет ничего общего с заголовком HTML-страницы, то есть, с содержимым тега

<head></head>

это разные вещи. Итак, во Flask заголовок в ответ на запрос можно формировать тремя способами:

  1. В обработчике возвратить строку, тогда автоматически ответом будет content-type=text/html и код 200.
  2. Сформировать ответ с помощью функции make_response()
  3. Возвратить кортеж формата: (response, status, headers) или (response, headers)

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

from flask import Flask, render_template
 
app = Flask(__name__)
 
menu = [{"title": "Главная", "url": "/"},
        {"title": "Добавить статью", "url": "/add_post"}]
 
@app.route("/")
def index():
    return render_template('index.html', menu=menu, posts=[])
 
if __name__ == "__main__":
    app.run(debug=True)

Функция представления главной страницы возвращает текст (после обработки шаблона) и сервер (Flask) автоматически формирует ответ с параметрами:

  • content-type=text/html
  • code = 200

Функция make_response

Однако, если мы хотим самостоятельно определить некоторые параметры заголовка, то можно воспользоваться специальной функцией

res_obj = make_response(res_body, status_code=200)

которая возвращает ссылку на объект ответа. Здесь:

  • res_body – передаваемое содержимое (контент);
  • status_code – код ответа сервера (по умолчанию 200).

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

@app.route("/")
def index():
    content = render_template('index.html', menu=menu, posts=[])
    res = make_response(content)
    res.headers['Content-Type'] = 'text/plain'
    res.headers['Server'] = 'flasksite'
    return res

Смотрите, здесь параметр 'Content-Type' принимает значение 'text/plain', которое указывает браузеру отображать данные в виде обычного текста. После обновления главной страницы, увидим следующее:

Вся страница отображена в виде текста. Или, можно сделать так:

@app.route("/")
def index():
    img = None
    with app.open_resource( app.root_path + "/static/images/ava.png", mode="rb") as f:
       img = f.read()
 
    if img is None:
        return "None image"
 
    res = make_response(img)
    res.headers['Content-Type'] = 'image/png'
    return res

Мы здесь загружаем изображение из подкаталога static/images и отдаем его браузеру, указывая, что принятые данные следует представлять в виде png изображения. И, действительно, при обновлении страницы, увидим загруженное изображение:

Или, можно вернуть страницу с определенным кодом, отличным от 200, например:

@app.route("/")
def index():
    res = make_response("<h1>Ошибка сервера</h1>", 500)
    return res

Здесь генерируется ошибка 500 – внешняя ошибка сервера с заголовком h1. Вот так заголовок кардинально влияет на отображение данных в окне браузера.

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

Создание ответа с помощью кортежей

Последний способ создать ответ – использовать кортежи в одном из следующих форматов:

  • (response, status, headers)
  • (response, headers)
  • (response, status)

response — строка, представляющая собой тело ответа, status — код состояния HTTP, который может быть указан в виде целого числа или строки, а headers — словарь со значениями заголовков. Например, так:

@app.errorhandler(404)
def pageNot(error):
    return ("Страница не найдена", 404)

Мы здесь формирует ответ и указываем код ответа – 404 страница не найдена. Или, так:

@app.route("/")
def index():
    return "<h1>Main Page</h1>", 200, {'Content-Type': 'text/plain'}

Здесь после оператора return записан тоже кортеж только без круглых скобок. Последний параметр headers – словарь из набора параметров заголовка.

Создание 301 и 302 редиректов

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

  • 301 – страница перемещена на другой постоянный URL-адрес;
  • 302 – страница перемещена временно на другой URL-адрес.

Чтобы во Flask выполнить перенаправление с прежнего URL на новый, можно использовать функцию

redirect(location, status)

о которой мы уже говорили. В данном случае ее можно применить так:

@app.route('/transfer')
def transfer():
    return redirect(url_for('index'), 301)

Перехват запросов

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

@app.route("/")
def index():
    db = get_db()               # функции
    dbase = FDataBase(db)       # подключения к БД
    return render_template('index.html', menu=dbase.getMenu(), posts=dbase.getPostsAnonce())

И это не самое лучшее решение, оно нарушает один из основополагающих принципов программирования:

DRY – Don’t Repeat Yourself (не повторяйся)

Так вот, чтобы вынести за скобки этот общий код, можно воспользоваться специальными декораторами для перехвата запросов:

  • before_first_request – выполняет функцию до обработки первого запроса;
  • before_request – выполняет функцию до обработки текущего запроса;
  • after_request – выполняет функцию после обработки запроса (такая функция не вызывается при возникновении исключений в обработчике запросов);
  • teardown_request (похож на after_request) – вызванная функция всегда будет выполняться вне зависимости от того, возвращает ли обработчик исключение (ошибку) или нет.

Давайте для примера добавим эти обработчики в нашу программу и посмотрим последовательность их вызовов:

@app.before_first_request
def before_first_request():
    print("before_first_request() called")
 
@app.before_request
def before_request():
    print("before_request() called")
 
@app.after_request
def after_request(response):
    print("after_request() called")
    return response
 
@app.teardown_request
def teardown_request(response):
    print("teardown_request() called")
    return response

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

before_first_request() called
before_request() called
after_request() called
teardown_request() called

Обновим страницу и теперь увидим только три вызова:

before_request() called
after_request() called
teardown_request() called

При последующих вызовах (любых страниц) функция before_first_request вызвана уже не будет.

В последующих занятиях, развивая наш тестовый сайт, мы воспользуемся декоратором before_request, чтобы настраивать связь с БД до выполнения самого запроса.

Видео по теме