Spec-Zone.ru › Bottle 0.11

Рецепты

Это коллекция фрагментов кода и примеров для распространённых случаев использования.

Отслеживание сессий

Встроенной поддержки сессий нет, поскольку нет правильного способа сделать это (в микро-фреймворке). В зависимости от требований и среды, можно использовать 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API