Python: Веб-разработка без фреймворков (часть 3)
Прочитав предыдущие статьи читатель, надеюсь, убедился, что средств PythonPaste и WebOb более чем достаточно для разбора запроса, композиции приложений и выполнения множества стандартных задач. Далее мы будем рассматривать вопросы генерации ответов, развертывания на сервере и выбора различных вспомогательных библиотек и инструментов.
До сих пор в примерах мы почти не занимались собственно ответами, перекладывая эту задачу на какие-то уже существующие приложения. Как уже было упомянуто в описании стандарта WSGI, ответы можно генерировать по частям, однако, как правило, сначала приложение выполняет все нужные вычисления, затем подставляет результаты в шаблон и возвращает полученный документ. Для такой модели работы с ответами хорошо подходит класс webob.Response. Впрочем, он также имеет поддержку последовательной генерации (см. app_iter), просто о ней мы говорить не будем.
webob.Response
Первое, что нужно понять, это то, что экземпляры этого класса являются WSGI приложениями. Создав и наполнив такой экземпляр данными мы получаем WSGI приложение, которое можно использовать единожды или многократно, по желанию. Давайте разберем его использование на примере (внимание, используется декоратор из прошлой статьи):
from webob import Response
@webob_wrap
def hello_app(req):
if 'name' in req.params:
if req.charset is None:
req.charset = 'UTF-8'
print req.user_agent, 'sucks'
return Response(unicode_body=u'Hello, %(name)s!' % req.params)
else:
return form_app
form_app = Response('<form method=POST><input name=name><input type=submit>')
Хотелось бы сказать пару теплых слов о поддержке браузерами Unicode в запросах: ни IE, ни столь горячо любимый всеми Firefox не способны указать в какой кодировке были отправлены данные формы (если ваш браузер с этой задачей справляется, напишите, пожалуйста, в комментариях). Поэтому, чтобы получать данные формы как положено, в Unicode, нам пришлось добавить несколько строк, которые устанавливают кодировку запроса по умолчанию в UTF-8. Место этих строк, конечно, в нашем незабвенном декораторе, поэтому давайте туда их и перенесем. В результате тело hello_app укоротилось до четырех строк:
@webob_wrap
def hello_app(req):
if 'name' in req.params:
return Response(unicode_body=u'Hello, %(name)s!' % req.params)
else:
return form_app
Как видно из примера, ответ можно и даже следует создавать из Unicode строк — HTTP ответ будет сгенерирован корректно. Также видно, что экземпляры Response «многоразовые» (см. выше: form_app).
Постепенное наполнение ответа данными
Response не обязательно наполнять данными при инициализации. Можно делать это постепенно, например вот так:
@webob_wrap
def hello_app_cookie(req):
r = Response(charset='UTF-8')
if 'name' in req.params:
name = req.params['name']
r.set_cookie('name', name)
elif 'name' in req.cookies:
name = req.cookies['name']
#r.delete_cookie('name')
else:
return form_app
r.unicode_body = u'Hello, %s!' % name
return r
В целом это понятно из кода, но на всякий случай обращу внимание, что мы теперь используем cookies для запоминания имени пользователя и опять-таки все значения Unicode, как и должно быть.
Совсем несложно контролировать кеширование (.cache_expires) и условные ответы. Делать это самостоятельно весьма трудоемко, и мало какой фреймворк предоставляет для этого средства. Итак, добавив лишь пару строк, мы получаем поддержку Etag / If-None-Match:
r.conditional_response = True
r.md5_etag()
return r
Тут вызов md5_etag() вычисляет Etag как MD5 хеш тела ответа, но при желании etag можно устанавливать самостоятельно. Ничуть не сложнее работать с If-Modified-Since, для этого у ответа устанавливается .last_modified. Таким образом, хоть мы и сгенерировали ответ целиком, он будет передан клиенту только в том случае, если его кеш устарел.
Request.get_response
Как правило, мы получаем экземпляры Request и строим экземпляры Response, но иногда нам понадобится делать всё ровно наоборот, а именно для тестирования и для написания middleware. Создать новый экземпляр Request не имея полного WSGI окружения очень просто:
test_req = Request.blank('/?name=John')
Естественно, можно наполнить запрос данными присваивая значения соответствующим атрибутам. Использовать же такой объект можно вовсе не только с теми функциями и методами которые готовы работать с WebOb. На самом деле мы можем вызвать с его помощью любое WSGI приложение и получить результат в удобном нам виде, а именно Response:
test_response = test_req.get_response(hello_app_cookie)
assert isinstance(test_response, Response)
assert test_response.body == 'Hello, John!'
print test_response
Весьма удобно, что Response.__str__ возвращает строку с соответствующим HTTP ответом:
200 OK
content-type: text/html; charset=UTF-8
Set-Cookie: name="John"; Path=/
Content-Length: 12
ETag: mE3orpck0aUrpsNFPcMzzw
Hello, John!
Как уже упоминалось, такой подход работает для любых WSGI приложений:
>>> from webob import Request
>>> from paste.fileapp import DataApp
>>> dataapp = DataApp('alert("foo")', [('Content-type', 'text/javascript')])
>>> print Request.blank('/').get_response(dataapp)
200 OK
Content-type: text/javascript
Accept-Ranges: bytes
Last-Modified: Sun, 13 Apr 2008 15:40:34 GMT
ETag: 1208101234.52-12
Content-Range: bytes 0-11/12
Content-Length: 12
alert("foo")
Далее
Следующей будет статья про конфигурацию приложений и использование mod_wsgi. Также в этой серии запланированы статьи о написании middleware, с рекомендациями по работе с шаблонами и ORM а также небольшая обзорная статья о популярных фреймворках.
Все про українське ІТ в телеграмі — підписуйтеся на канал DOU
12 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.