Сообщить об ошибке.

Получение данных из запроса в приложении на Flask

В материале рассматривается работа с контекстом запроса в приложении на Flask и доступ к различным данным запроса, а именно доступ к полям формы, дополнительным параметрам URL-адреса, получение данных JSON, извлечения информации о User-Agent и IP-адреса клиента, получение referrer-URL и так далее.

Содержание:


Доступ к параметрам, передаваемых в URL GET запросом.

Параметры GET запроса, передаются в качестве параметров URL-адреса. Это та часть URL- адреса, которая расположена после знака вопроса, например http://example.ru/search?query=str.lower&opt=9, параметрами запроса будут считаться query=str.lower&opt=9. Такие адреса в основном используются в поисковых запросах, REST API сайта или в AJAX-запросах.

Для получения этих параметров необходимо воспользоваться свойством args входящего запроса flask.Request. По умолчанию, свойство request.args, представляет собой объект словаря, доступ к значениям параметров осуществляется по ключу request.args['opt'] Для безопасного извлечения ключей из словаря, что бы избежать исключений (при отсутствии ключа), можно воспользоваться методом dict.get(). Во Flask, синтаксис этого метода выглядит следующим образом:

request.args.get(key, default=None, type=None)

Где аргумент key - это ключ (имя параметра в URL); default - возвращаемое значение, если ключа key не существует. Пример извлечения параметров GET-запроса, если URL-адрес имеет вид http://example.ru/search?query=str.lower&opt=9:

from flask import request

@app.route('/search/', methods=['GET'])
def search():
    error = None
    query = request.args.get('query')
    # проверяем, передается ли параметр
    # 'query' в URL-адресе
    if query and query != '':
        # если `query`существует и это не пустая строка,
        # то можно приступать к обработке запроса
        opt = request.args.get('opt', default=0, type=int)
        # возвратит `query=str.lower, opt=9`
        return f"query={query}, opt={opt}"
    else:
        # если `query` не существует или это пустая строка, то 
        # отображаем форму поискового запроса с сообщением.
        error = 'Не введен запрос!'
        return render_template('search.html', error=error)

Если важно сохранение порядка передаваемых параметров, то можно изменить тип данных, присвоив request.parameter_storage_class другой тип, например werkzeug.datastructures.ImmutableList.

Доступ к данным формы, передаваемой POST запросом.

Для доступа к данным формы (передаваемым в запросах POST или PUT) нужно использовать атрибут входящего запроса request.form, который так же представляет собой словароподобный объект. Ключи request.form имеют значения атрибута name HTML-тэга <input> в форме (например <input name="username">).

Так как request.form представляет собой словароподобный объект, то к нему рекомендуется обращаться с помощью метода словаря dict.get() или путем перехвата KeyError и выдачи собственной страницы с ошибкой HTTP 404 Not Found.

В примере, для извлечения полей формы, воспользуемся методом request.args.get(key, default=None, type=None). Предположим, что была передана форма авторизации пользователя на сайте:

from flask import request

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form.get('username'),
                       request.form.get('password')):
            # если условие if выполнилось успешно, то 'username' 
            # существует и дальше к нему можно обращаться как  
            # к ключу словаря, что на много быстрее.
            return log_the_user_in(request.form['username'])
        else:
            error = 'Неверное имя пользователя или пароль'
    return render_template('login.html', error=error)

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

Примечание: объект запроса flask.Request еще содержит атрибут request.values, который объединяет в себе значения, передаваемые GET и POST запросами. Другими словами, используя request.values.get() можно получить доступ как к элементам передаваемой формы в HTML-полях <input> , так и параметрам, передаваемым в URL-адресе.

Обратите внимание, что атрибут объекта запроса request.method содержит в себе строковое значение HTTP-метода.

Получение текущего URL-адреса страницы.

Так как URL-адрес может иметь переменные части, которые указываются в угловых скобках, иногда (например, в целях сбора статистики) необходимо получить его оригинал, что бы потом, в функции-представлении, не воссоздавать его вручную.

Объект запроса flask.Request позволяет это сделать. За URL-адрес в объекте запроса отвечают несколько атрибутов:

  • request.url - полный URL-адрес запроса со схемой request.scheme, хостом request.host_url, корневым путем request.root_path, путем request.path и строкой запроса request.query_string.
  • request.base_url - как request.url, но без строки запроса.
  • request.url_root - URL-адрес со схемой request.scheme, хостом request.host_url, корневым путем request.root_path.
  • request.scheme - схема URL-адресов (http или https).
  • request.host - только хост, включая порт, если он доступен.
  • request.root_path - префикс, под которым монтируется приложение, без завершающей косой черты (еще известен как environ["SCRIPT_ROOT"]).
  • request.path - часть пути URL-адреса после root_path. Это путь, используемый для маршрутизации внутри приложения.
  • request.full_path - запрошенный путь URL, включая строку запроса (параметры URL, после знака вопроса).
  • request.query_string - часть URL-адреса после знака вопроса '?'.
  • request.url_charset - кодировка, которая предполагается для URL-адресов.

Разберем значение приведенных выше атрибутов объекта запроса на примере. Допустим, что приложение имеет следующий корень: http://www.example.com/myapp и пользователь запрашивает следующий URI-адрес: http://www.example.com/myapp/%CF%80/page.html?x=y

В этом случае значения вышеупомянутых атрибутов будут следующими:

  • request.url: 'http://www.example.com/myapp/π/page.html?x=y';
  • request.base_url: 'http://www.example.com/myapp/π/page.html';
  • request.url_root: 'http://www.example.com/myapp/'
  • request.scheme: 'http';
  • request.host: 'www.example.com';
  • request.root_path: '/myapp';
  • request.path: '/π/page.html';
  • request.full_path: '/π/page.html?x=y';
  • request.query_string: x=y.

Информация об окружении WSGI сервера.

Если запуск и работоспособность приложение Flask обеспечивается WSGI сервером (например gunicorn), то информацию об окружении этого сервера можно получить из атрибута объекта запроса request.environ. Этот атрибут представляет собой словарный объект и содержит информацию об окружении WSGI, а так же HTTP-заголовки, полученные вместе с запросом.

Переменные окружения WSGI сервера.

Из словарного атрибута запроса request.environ можно узнать очень много интересного, например реферальный URL-адрес (это адрес, с которого клиент перешел на текущий URL), его ключ в окружении 'HTTP_REFERER'. Возвращаемые ключи словарного объекта request.environ во многом зависят от того где работает и как настроен WSGI сервер, т.е. здесь все индивидуально.

Вот некоторые из них:

'wsgi.version', 'PATH_INFO''HTTP_SEC_CH_UA'
'wsgi.url_scheme''QUERY_STRING''HTTP_SEC_CH_UA_MOBILE'
'wsgi.input''REQUEST_URI''HTTP_SEC_CH_UA_PLATFORM'
'wsgi.errors''RAW_URI''HTTP_UPGRADE_INSECURE_REQUESTS'
'wsgi.multithread''REMOTE_ADDR''HTTP_DNT'
'wsgi.multiprocess''REMOTE_PORT''HTTP_USER_AGENT'
'wsgi.run_once''SERVER_NAME''HTTP_ACCEPT'
'werkzeug.socket''SERVER_PORT''HTTP_REFERER'
'SERVER_SOFTWARE''SERVER_PROTOCOL''HTTP_ACCEPT_ENCODING'
'REQUEST_METHOD''HTTP_HOST''HTTP_ACCEPT_LANGUAGE'
'SCRIPT_NAME''HTTP_CONNECTION''HTTP_COOKIE'

Только не забывайте при обращении к словарному атрибуту request.environ, применять метод dict.get(), что бы избежать появления исключения, если какой-то ключ отсутствует.

Реальный IP-адрес клиента.

Переменные WSGI сервера необходимы, например, что бы узнать реальный IP-адрес посетителя сайта, если приложение Flask работает за прокси сервером, таким как Nginx. В ситуации с прокси, атрибут запроса request.remote_addr будет возвращать IP-адрес самого прокси сервера (т. е. локальный IP-адрес, на котором он работает, например 127.0.0.1), а не клиента. Реальный IP-адрес клиента можно узнать из окружения WSGI, обратившись к ключу 'HTTP_X_FORWARDED_FOR' или 'X-Real-IP', это зависит, какая переменная использовалась при настройке Nginx:

# если `HTTP_X_FORWARDED_FOR` не определен, 
# то просто возвращаем `request.remote_addr`
request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr)

Примечание. Заголовок X-Forwarded-For (XFF) является де-факто стандартным заголовком для идентификации происхождения IP-адреса клиента, подключающегося к веб-серверу через HTTP-прокси или балансировщик нагрузки. Когда трафик перехватывается между клиентом и сервером, то журналы доступа к серверу содержат только IP-адрес прокси-сервера или балансировки нагрузки.

И вообще, при связке: прокси сервер -> WSGI сервер -> приложение Flask, максимум информации лучше брать из окружения request.environ, т.к. здесь она будет более правдива.

Получение данных в формате JSON.

Что бы извлечь JSON данные, передаваемые в запросе необходимо воспользоваться методом объекта запроса .get_json(), который имеет следующий синтаксис:

data = request.get_json(force=False, silent=False, cache=True)

Принимаемые аргументы:

  • force: (bool) - игнорирует тип mimetype и всегда пытается анализировать JSON.
  • silent: (bool) - отключает появление возможных ошибок при анализе JSON и если они происходят то возвращает None.
  • cache: (bool) - сохраняет проанализированный JSON для последующих вызовов.

Если заголовок mimetype не указывает на JSON данные (application/json), то этот метод, по умолчанию (force=False), возвратит None.

Если синтаксический анализ JSON завершается неудачно, то вызывается функция request.on_json_loading_failed(), и в качестве возвращаемого значения используется ее значение.

Другие полезные атрибуты объекта запроса.

request.headers:

Атрибут объекта запроса request.headers содержит все заголовки, полученные вместе с запросом. Представляет собой словарный объект, следовательно, для безопасного получения заголовка можно воспользоваться методом словаря dict.get():

request.headers.get('HEADER-NAME')

Если заголовок с именем 'HEADER-NAME' не существует то код выше вернет None (исключения не будет), поэтому его можно использовать в конструкции:

if request.headers.get('HEADER-NAME'):
    ...

Но прямое обращение к ключу приведет к появлению исключения KeyError: 'HEADER-NAME'

if request.headers['HEADER-NAME']:
    ...

Если необходимо обращаться к request.headers без использования метода dict.get(), то используйте следующую конструкцию:

if 'HEADER-NAME' in request.headers:
   customHeader = request.headers['HEADER-NAME']
    ...

request.cookies:

Атрибут объекта запроса request.cookies содержит все файлы cookie, переданных вместе с запросом. Представляет собой словарный объект.

cookie = request.cookies.get('cookie-name'):

Методику работы с объектом request.cookies смотрите на примере request.headers

request.referrer:

Атрибут объекта запроса request.referrer содержит поле заголовка запроса, которое позволяет клиенту указать, в интересах сервера, адрес ресурса, с которого клиент перешел на текущий URL.

referrer = request.referrer

# что эквивалентно получению заголовка

referrer = request.headers.get('Referer')

request.user_agent:

Атрибут объекта запроса request.user_agent представляет собой данные заголовка, содержащие сведения о пользовательском агенте.

Чтобы получить значение заголовка, необходимо использовать выражение: request.user_agent.string.

agent_string = request.user_agent.string

# что эквивалентно получению заголовка

request.headers.get('User-Agent')