Рецепты
Это коллекция фрагментов кода и примеров для распространённых случаев использования.
Отслеживание сессий
Встроенной поддержки сессий нет, поскольку нет правильного способа сделать это (в микро-фреймворке). В зависимости от требований и среды, можно использовать middleware beaker с подходящим бэкэндом или реализовать её самостоятельно. Вот пример сессий beaker с файловым бэкэндом:
import bottle
from beaker.middleware import SessionMiddleware
session_opts = {
'session.type': 'file',
'session.cookie_expires': 300,
'session.data_dir': './data',
'session.auto': True
}
app = SessionMiddleware(bottle.app(), session_opts)
@bottle.route('/test')
def test():
s = bottle.request.environ.get('beaker.session')
s['test'] = s.get('test',0) + 1
s.save()
return 'Test counter: %d' % s['test']
bottle.run(app=app)
Отладка со стилем: Отладка middleware
Bottle перехватывает все исключения, поднятые в вашем приложении, чтобы предотвратить сбой вашего WSGI-сервера. Если встроенный режим debug() недостаточен и вам нужно, чтобы исключения распространялись на отладочный middleware, можно отключить это поведение:
import bottle app = bottle.app() app.catchall = False #Now most exceptions are re-raised within bottle. myapp = DebuggingMiddleware(app) #Replace this with a middleware of your choice (see below) bottle.run(app=myapp)
Теперь Bottle перехватывает только свои собственные исключения (HTTPError, HTTPResponse и BottleException), а ваш middleware может обрабатывать остальные.
Библиотеки werkzeug и paste поставляются с очень мощным отладочным WSGI-middleware. Обратитесь к werkzeug.debug.DebuggedApplication для werkzeug и paste.evalexception.middleware.EvalException для paste. Они оба позволяют вам исследовать стек и даже выполнять код Python в контексте стека, поэтому не используйте их в продакшене.
Тестирование Bottle-приложений на единицу
Тестирование на единицу обычно выполняется относительно методов, определённых в вашем веб-приложении, без запуска WSGI-среды.
Простой пример с использованием Nose:
import bottle
@bottle.route('/')
def index():
return 'Hi!'
if __name__ == '__main__':
bottle.run()
Скрипт теста:
import mywebapp
def test_webapp_index():
assert mywebapp.index() == 'Hi!'
В примере метод Bottle route() никогда не выполняется — тестируется только index().
Функциональное тестирование Bottle-приложений
Любая система HTTP-тестирования может использоваться с работающим WSGI-сервером, но некоторые фреймворки тестирования более тесно взаимодействуют с WSGI и предоставляют возможность вызывать WSGI-приложения в контролируемой среде, с трассировками и полным использованием средств отладки. Инструменты тестирования для WSGI — хорошая отправная точка.
Пример с использованием WebTest и Nose:
from webtest import TestApp
import mywebapp
def test_functional_login_logout():
app = TestApp(mywebapp.app)
app.post('/login', {'user': 'foo', 'pass': 'bar'}) # log in and get a cookie
assert app.get('/admin').status == '200 OK' # fetch a page successfully
app.get('/logout') # log out
app.reset() # drop the cookie
# fetch the same page, unsuccessfully
assert app.get('/admin').status == '401 Unauthorized'
Встраивание других WSGI-приложений
Это не рекомендуемый способ (вы должны использовать middleware перед bottle для этого), но вы можете вызвать другие WSGI-приложения из вашего Bottle-приложения и позволить Bottle действовать как псевдо-middleware. Вот пример:
from bottle import request, response, route
subproject = SomeWSGIApplication()
@route('/subproject/:subpath#.*#', method='ALL')
def call_wsgi(subpath):
new_environ = request.environ.copy()
new_environ['SCRIPT_NAME'] = new_environ.get('SCRIPT_NAME','') + '/subproject'
new_environ['PATH_INFO'] = '/' + subpath
def start_response(status, headerlist):
response.status = int(status.split()[0])
for key, value in headerlist:
response.add_header(key, value)
return app(new_environ, start_response)
Опять же, это не рекомендуемый способ реализации подпроектов. Он здесь только потому, что многие люди просили об этом и для демонстрации того, как bottle отображается в WSGI.
Игнорирование слеша в конце строки
Для Bottle, /example и /example/ — это две разные маршрутизации [1]. Чтобы обработать оба URL одинаково, можно добавить два декоратора @route:
@route('/test')
@route('/test/')
def test(): return 'Slash? no?'
или добавить WSGI-middleware, который удаляет слеши в конце всех URL:
class StripPathMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, e, h):
e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
return self.app(e,h)
app = bottle.app()
myapp = StripPathMiddleware(app)
bottle.run(app=myapp)
Примечания
| [1] | Потому что они таковы. См. <http://www.ietf.org/rfc/rfc3986.txt> |
Keep-alive запросы
Примечание
Для более подробного объяснения см. Введение в асинхронные приложения.
Несколько «push» механизмов, таких как XHR multipart, нуждаются в возможности записи данных ответа без закрытия соединения в сочетании с заголовком ответа «Connection: keep-alive». WSGI не легко поддаётся этому поведению, но всё же возможно сделать это в Bottle с помощью асинхронного фреймворка gevent. Вот пример, который работает как с HTTP-сервером gevent, так и с HTTP-сервером paste (может работать и с другими, но я не пробовал). Просто измените server='gevent' на server='paste' для использования сервера paste:
from gevent import monkey; monkey.patch_all()
import time
from bottle import route, run
@route('/stream')
def stream():
yield 'START'
time.sleep(3)
yield 'MIDDLE'
time.sleep(5)
yield 'END'
run(host='0.0.0.0', port=8080, server='gevent')
Если вы перейдёте по ссылке http://localhost:8080/stream, вы должны увидеть «START», «MIDDLE» и «END», появляющиеся по одному (а не ждать 8 секунд, чтобы увидеть их все сразу).
Сжатие gzip в Bottle
Примечание
Для подробного обсуждения см. сжатие
Частая просьба состоит в том, чтобы Bottle поддерживал сжатие Gzip, что ускоряет сайты, сжимая статические ресурсы (такие как CSS и JS файлы) во время запроса.
Поддержка сжатия gzip — непростая задача из-за ряда часто возникающих граничных случаев. Правильная реализация gzip должна:
- Сжимать на лету и быть быстрой при этом.
- Не сжимать для браузеров, которые этого не поддерживают.
- Не сжимать файлы, которые уже сжаты (изображения, видео).
- Не сжимать динамические файлы.
- Поддерживать два разных алгоритма сжатия (gzip и deflate).
- Кэшировать сжатые файлы, которые часто не меняются.
- Деактивировать кэш, если какой-либо из файлов изменился.
- Убедиться, что кэш не становится слишком большим.
- Не кэшировать небольшие файлы, потому что поиск по диску займёт больше времени, чем сжатие на лету.
В связи с этими требованиями проект Bottle рекомендует, что сжатие gzip лучше всего обрабатывается WSGI-сервером, на котором работает Bottle. Такие WSGI-серверы, как cherrypy, предоставляют middleware GzipFilter, которое можно использовать для этого.
Использование плагина hooks
Например, если вы хотите разрешить Cross-Origin Resource Sharing для контента, возвращаемого всеми URL, вы можете использовать декоратор hook и настроить функцию обратного вызова:
from bottle import hook, response, route
@hook('after_request')
def enable_cors():
response.headers['Access-Control-Allow-Origin'] = '*'
@route('/foo')
def say_foo():
return 'foo!'
@route('/bar')
def say_bar():
return {'type': 'friendly', 'content': 'Hi!'}
Вы также можете использовать before_callback для выполнения действия перед вызовом каждой функции.
Использование Bottle с Heroku
Heroku, популярная платформа облачных приложений, теперь предоставляет поддержку запуска Python-приложений на своей инфраструктуре.
Этот рецепт основан на Heroku Quickstart, с заменой кода, специфичного для Bottle, на раздел «Напишите своё приложение» Руководства по началу работы с Python на Heroku/Cedar:
import os
from bottle import route, run
@route("/")
def hello_world():
return "Hello World!"
run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
Стек приложений Heroku передает порт, на котором приложение должно прослушивать запросы, используя словарь os.environ.
© 2009–2017 Marcel Hellkamp
Licensed under the MIT License.
https://bottlepy.org/docs/0.11/recipes.html