Рецепты
Это коллекция фрагментов кода и примеров для распространённых случаев использования.
Отслеживание сессий
Встроенной поддержки сессий нет, потому что нет правильного способа сделать это (в микрофреймворке). В зависимости от требований и среды можно использовать 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='ANY')
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_request для выполнения действия перед вызовом каждой функции.
Использование Bottle с Heroku
Heroku, популярная облачная платформа приложений, теперь поддерживает запуск приложений Python на своей инфраструктуре.
Этот рецепт основан на Heroku Quickstart, с заменой кода Bottle на Write Your App раздел Getting Started with Python on 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.12/recipes.html