秘诀¶
这里收集了一些常见用例的代码片段和例子.
使用Session¶
Bottle自身并没有提供Session的支持,因为在一个迷你框架里面,没有合适的方法来实现。根据需求和使用环境,你可以使用 beaker 中间件或自己来实现。下面是一个使用beaker的例子,Session数据存放在"./data"目录里面:
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)
警告:烧杯的会话中间件不是线程安全的。如果两个并发请求同时修改同一个会话,其中一个更新可能会丢失。因此,会话只应填充一次,然后作为只读存储处理。如果您发现自己定期更新会话,并且不想冒丢失任何更新的风险,那么可以考虑使用真正的数据库,或者寻找其他会话中间件库。
Debugging with Style: 调试中间件¶
Bottle捕获所有应用抛出的异常,防止异常导致WSGI服务器崩溃。如果内置的 debug()
模式不能满足你的要求,你想在你自己写的中间件里面处理这些异常,那么你可以关闭这个功能。
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
),你的中间件可以处理剩下的那些异常。
werkzeug 和 paste 这两个第三方库都提供了非常强大的调试中间件。如果是 werkzeug ,可看看 werkzeug.debug.DebuggedApplication
,如果是 paste ,可看看 paste.evalexception.middleware.EvalException
。它们都可让你检查运行栈,甚至在保持运行栈上下文的情况下,执行Python代码。所以 不要在生产环境中使用它们 。
Bottle 应用单元测试¶
Unit测试一般用于测试应用中的函数,但不需要一个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.request
你可以用 Boddle ::
import bottle
@bottle.route('/')
def index():
return 'Hi %s!' % bottle.request.params['name']
测试代码:
import mywebapp
from boddle import boddle
def test_webapp_index():
with boddle(params={'name':'Derek'}):
assert mywebapp.index() == 'Hi Derek!'
功能测试¶
任何基于HTTP的测试系统都可用于测试WSGI服务器,但是有些测试框架与WSGI服务器工作得更好。它们可以在一个可控环境里运行WSGI应用,充分利用traceback和调试工具。 Testing tools for WSGI 是一个很好的上手工具。
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
assert app.get('/logout').status_code == 200 # log out
app.reset() # drop the cookie
# fetch the same page, unsuccessfully
assert app.get('/admin', expect_errors=True).status == '401 Unauthorized'
嵌入其他WSGI应用¶
并不建议你使用这个方法,你应该在Bottle前面使用一个中间件来做这样的事情。但你确实可以在Bottle里面调用其他WSGI应用,让Bottle扮演一个中间件的角色。下面是一个例子。
from bottle import request, response, route
subproject = SomeWSGIApplication()
@route('/subproject/<subpath:re:.*>', 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/
是两个不同的route 1 。为了一致对待这两个URL,你应该添加两个route。
@route('/test')
@route('/test/')
def test(): return 'Slash? no?'
添加一个wsgi中间件,从所有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)
或添加 before_request
钩住以去除尾随斜杠:
@hook('before_request')
def strip_path():
request.environ['PATH_INFO'] = request.environ['PATH_INFO'].rstrip('/')
脚注
- 1
因为确实如此,见 <http://www.ietf.org/rfc/rfc3986.txt>
Keep-alive 请求¶
注解
详见 异步应用入门 。
像XHR这样的"push"机制,需要在HTTP响应头中加入 "Connection: keep-alive" ,以便在不关闭连接的情况下,写入响应数据。WSGI并不支持这种行为,但如果在Bottle中使用 gevent 这个异步框架,还是可以实现的。下面是一个例子,可配合 gevent HTTP服务器或 paste HTTP服务器使用(也许支持其他服务器,但是我没试过)。在run()函数里面使用 server='gevent'
或 server='paste'
即可使用这两种服务器。
from gevent import monkey; monkey.patch_all()
import gevent
from bottle import route, run
@route('/stream')
def stream():
yield 'START'
gevent.sleep(3)
yield 'MIDDLE'
gevent.sleep(5)
yield 'END'
run(host='0.0.0.0', port=8080, server='gevent')
通过浏览器访问 http://localhost:8080/stream
,可看到'START','MIDDLE',和'END'这三个字眼依次出现,一共用了8秒。
Gzip压缩¶
注解
详见 compression
Gzip压缩,可加速网站静态资源(例如CSS和JS文件)的访问。人们希望Bottle支持Gzip压缩,(但是不支持)......
支持Gzip压缩并不简单,一个合适的Gzip实现应该满足以下条件。
压缩速度要快
如果浏览器不支持,则不压缩
不压缩那些已经充分压缩的文件(图像,视频)
不压缩动态文件
支持两种压缩算法(gzip和deflate)
缓存那些不经常变化的压缩文件
不验证缓存中那些已经变化的文件(De-validate the cache if one of the files changed anyway)
确保缓存不太大
不缓存小文件,因为寻道时间或许比压缩时间还长
因为有上述种种限制,建议由WSGI服务器来处理Gzip压缩而不是Bottle。像 cherrypy 就提供了一个 GzipFilter 中间件来处理Gzip压缩。
使用钩子¶
例如,你想提供跨域资源共享,可参考下面的例子。
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
,这样在route的回调函数被调用之前,都会调用你的钩子函数。
在Heroku中使用Bottle¶
Heroku, 一个流行的云应用程序平台现在支持在其基础设施上运行Python应用程序。
这份教程基于 Heroku Quickstart, 用Bottle特有的代码替换了 Getting Started with Python on Heroku/Cedar 中的`Write Your App <http://devcenter.heroku.com/articles/python#write_your_app>`_ 这部分:
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 字典来提供Bottle应用需要监听的端口。