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

Смотреть материал на YouTube | RuTube

Прежде чем идти дальше, нам нужно разобраться со способами формирования ответа сервера во 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, чтобы настраивать связь с БД до выполнения самого запроса.

Видео по теме