Руководство по Python Bottle (Перевод) Часть 5 - Request Data (данные в запросе)
В предыдущей части (Руководство по Python Bottle (Перевод) Часть 4 — Создание контента) мы рассмотрели основные аспекты относящиеся к передаваемому контенту, в этой части мы рассмотрим данные передаваемые в запросах.
Файлы Cookie, HTTP-заголовки, поля HTML форм и другие данные запроса доступны через глобальный объект request. Этот специальный объект всегда ссылается на текущий запрос, даже в многопоточных средах, где одновременно обрабатываются сразу несколько клиентских подключений:
from bottle import request, route, template
@route('/hello')
def hello():
name = request.cookies.username or 'Guest'
return template('Привет {{name}}', name=name)
Объект request является подклассом от BaseRequest и имеет очень богатый API для доступа к данным. Здесь будут рассмотрены только наиболее часто используемые функции, этого должно быть достаточно для начала.
Введение в FORMSDICT (INTRODUCING FORMSDICT)
Bottle использует специальный тип словаря для хранения данных форм и файлов Сookie. FormsDict ведет себя как обычный словарь, но имеет некоторые дополнительные функции, облегчающие жизнь разработчика.
Доступ к атрибутам: все значения в словаре доступны как атрибуты. Эти виртуальные атрибуты возвращают строки Unicode, даже если значение отсутствует или не удается декодировать Unicode. В этом случае строка пуста, но все еще существует:
name = request.cookies.name
# это ссылка вот к этому:
name = request.cookies.getunicode('name') # encoding='utf-8' (default)
# которая по умолчанию делает это:
try:
name = request.cookies.get('name', '').decode('utf-8')
except UnicodeError:
name = u''
Несколько значений на ключ: FormsDict является подклассом MultiDict и может хранить более одного значения на ключ. Стандартные методы доступа к словарю будут возвращать только одно значение, но метод getall() возвращает (в том числе и пустой) список всех значений для определенного ключа:
for choice in request.forms.getall('multiple_choice'):
do_something(choice)
Поддержка WTForms: Некоторые библиотеки (например, WTForms) требуют использовать словари с Unicode в качестве входных данных. FormsDict.decode() сделает это за нас. Он декодирует все значения и возвращает свою копию, сохраняя при этом несколько значений для каждого ключа и все другие полезности.
Примечание:
В Python 2 все ключи и значения являются байтовыми строками. Если нужен Unicode, можно вызвать FormsDict.getunicode () или получить значения через доступ к атрибуту. Оба метода попытаются декодировать строку (по умолчанию: utf8) и возвратят пустую строку, если это не удастся. Нет необходимости отлавливать UnicodeError:
>>> request.query['city'] 'G\xc3\xb6ttingen' # Строка utf8 byte string >>> request.query.city u'Göttingen' # Идентичная строка в unicode
В Python 3 все строки являются Unicode, но в HTTP протоколе передача основана на обмене массивом byte. Сервер должен каким-то образом декодировать байтовые строки перед их передачей в приложение. Чтобы обезопасить процесс, WSGI предлагает ISO-8859-1 (он же latin1), обратимый однобайтовый кодек, который можно перекодировать с другой кодировкой. Bottle делает это для FormsDict.getunicode() и доступа к атрибутам, но не для методов dict-access. Они возвращают неизмененные значения, предоставленные реализацией сервера, что, вероятнее всего, не то, что нам нужно.
>>> request.query['city'] 'Göttingen' # Строка utf8 декодированная как ISO-8859-1 сервером >>> request.query.city 'Göttingen' # Та же строка корректно декодированная в utf8 движком Bottrle
Если необходим весь словарь с правильно декодированными значениями (например, для WTForms), можно вызвать FormsDict.decode (), для того чтобы получить перекодированную копию.
Куки (COOKIES)
Файлы Cookie — это небольшие фрагменты текста, которые хранятся на стороне браузеров клиентов и отправляются обратно на сервер при каждом запросе. Они полезны для сохранения некоторого состояния более чем для одного запроса (сам HTTP не имеет состояния), но не должны использоваться для вещей, связанных с безопасностью. Они могут быть легко подделаны клиентом.
Все куки, отправленные клиентом, доступны через BaseRequest.cookies (FormsDict). В этом примере показан простой счетчик просмотров на основе файлов Cookie:
from bottle import route, request, response
@route('/counter')
def counter():
count = int( request.cookies.get('counter', '0') )
count += 1
response.set_cookie('counter', str(count))
return 'Вы посетили эту страниц %d раз' % count
Метод BaseRequest.get_cookie() — это другой способ доступа к файлам Cookie. Он поддерживает декодирование подписанных файлов Cookie.
HTTP Заголовки (HTTP Headers)
Все HTTP заголовки, отправленные клиентом (например, Referer, Agent или Accept-Language), хранятся в WSGIHeaderDict и доступны через атрибут BaseRequest.headers. WSGIHeaderDict — это словарь с ключами без учета регистра:
from bottle import route, request
@route('/is_ajax')
def is_ajax():
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return 'Это AJAX запрос'
else:
return 'Это обычный запрос'
Переменные строки запроса (QUERY VARIABLES)
Строки запроса ( такие как /forum?Id=1&page=5) обычно используются для передачи небольшого числа пар ключ/значение на сервер. Вы можете использовать атрибут BaseRequest.query (FormsDict) для доступа к этим значениям и атрибут BaseRequest.query_string для получения всей строки.
from bottle import route, request, response, template
@route('/forum')
def display_forum():
forum_id = request.query.id
page = request.query.page or '1'
return template('Форум номер: {{id}} (страница {{page}})', id=forum_id, page=page)
Обработка HTML форм (HTML FORM HANDLING)
Давайте начнем с самого начала. В HTML типичная форма form выглядит примерно так:
<form action="/login" method="post">
Username: <input name="username" type="text" />
Password: <input name="password" type="password" />
<input value="Login" type="submit" />
</form>
Атрибут action указывает URL-адрес, который будет получать данные формы. Атрибут method определяет метод HTTP для использования (GET или POST). При использовании метода method=«get» значения формы добавляются к URL-адресу и доступны через BaseRequest.query, как описано выше. Это считается небезопасным и имеет ряд ограничений, поэтому здесь мы используем method=«post». В случае сомнений что использовать, используйте метод POST.
Поля формы, данные передаваемые через POST, хранятся в BaseRequest.forms как FormsDict. Код на стороне сервера может выглядеть так:
from bottle import route, request
@route('/login')
def login():
return '''
<form action="/login" method="post">
Username: <input name="username" type="text" />
Password: <input name="password" type="password" />
<input value="Login" type="submit" />
</form>
'''
@route('/login', method='POST')
def do_login():
username = request.forms.get('username')
password = request.forms.get('password')
if check_login(username, password):
return "<p>Авторизационные данные верны.</p>"
else:
return "<p>Авторизационные данные не верны.</p>"
Есть несколько других атрибутов, используемых для доступа к данным формы. Некоторые из них объединяют значения из разных источников для более легкого доступа.
Attribute GET Form fields POST Form fields File Uploads BaseRequest.query yes no no BaseRequest.forms no yes no BaseRequest.files no no yes BaseRequest.params yes yes no BaseRequest.GET yes no no BaseRequest.POST no yes yes
Загрузка файлов (FILE UPLOADS)
Для загрузки файлов, нам необходимо немного изменить тег form. Сначала мы сообщаем браузеру, что нужно кодировать данные формы другим способом, добавляя атрибут enctype=«multipart/form-data» в тег form. Затем мы добавляем теги input type=«file», чтобы пользователь мог выбрать файл. Пример:
<form action="/upload" method="post" enctype="multipart/form-data">
Категория: <input type="text" name="category" />
Выберите файл: <input type="file" name="upload" />
<input type="submit" value="Загрузить файл" />
</form>
Bottle хранит загруженные файлы в BaseRequest.files как экземпляры FileUpload вместе с некоторыми метаданными о загрузке. Вот так просто, вы можете сохранить файл на диск:
@route('/upload', method='POST')
def do_upload():
category = request.forms.get('category')
upload = request.files.get('upload')
name, ext = os.path.splitext(upload.filename)
if ext not in ('.png','.jpg','.jpeg'):
return 'Расширение файла не поддерживается.'
save_path = get_save_path_for_category(category)
upload.save(save_path) # appends upload.filename automatically
return 'OK'
FileUpload.filename содержит имя файла в файловой системе клиента, которое очищается и нормализуется для предотвращения ошибок, связанных с неподдерживаемыми символами или сегментами пути в имени файла. Если вам необходимо первоначальное имя файла, отправленное клиентом, то его можно узнать в FileUpload.raw_filename.
Метод FileUpload.save настоятельно рекомендуется, если вы хотите сохранить файл на диск. Он предотвращает некоторые распространенные ошибки (например, он не перезаписывает существующие файлы, если вы не сообщите об этом) и сохраняет файл с минимальным потреблением памяти. Вы можете получить доступ к объекту файла напрямую через FileUpload.file. Но при этом следует быть предельно осторожными.
JSON CONTENT
Некоторые jаvascript или REST клиенты отправляют контент в application/json на сервер. Атрибут BaseRequest.json содержит формализованную структуру данных, если она доступна.
Получение необработанных данных (THE RAW REQUEST BODY)
Вы можете получить доступ к необработанным данным в виде файлового объекта через BaseRequest.body. Это буфер BytesIO или временный файл, в зависимости от длины содержимого и параметра BaseRequest.MEMFILE_MAX. В обоих случаях тело полностью буферизуется, прежде чем вы сможете получить доступ к атрибуту. Если вы ожидаете огромных объемов данных и хотите получить прямой небуферизованный доступ к потоку, то обратите внимание на запрос ['wsgi.input'].
WSGI Окружение (WSGI ENVIRONMENT)
Каждый экземпляр BaseRequest оборачивает словарь среды WSGI. Оригинал хранится в BaseRequest.environ, однако сам объект запроса также ведет себя как словарь. Большая часть данных предоставляется через специальные методы или атрибуты, но если вы хотите получить прямой доступ к переменным окружения WSGI, вы можете поступить так:
@route('/my_ip')
def show_ip():
ip = request.environ.get('REMOTE_ADDR')
# or ip = request.get('REMOTE_ADDR')
# or ip = request['REMOTE_ADDR']
return template("Your IP is: {{ip}}", ip=ip)
Продолжение:
Руководство по Python Bottle (Перевод) Часть 6 — Templates (о Шаблонах)