在 Flask 应用中使用 gevent

在 Flask 应用中使用 gevent

普通的 flask 应用

一般在用 python 开发 Flask web 应用时,使用 Flask 自带的调试模式可以给开发带来极大便利。Flask 自带的调试模式可让咱们在程序改动时自动从新加载咱们的应用程序,并且 jinja2 的模板也会随着改动自动刷新。通常用法是:html

# app.py
from flask import Flask
app = Flask( __name__ )

@app.route( '/')
def hello():
    return 'Hello World'

if __name__ == '__main__':
    app.run( debug = True )

运行上面这个例子,就能够在本地的 5000 端口运行由 flask 提供的服务器程序。若是咱们对这个文件进行修改,那么 flask 的底层框架 werkzuge 检测到文件变更后就会自动从新加载咱们的应用程序。python

然而 Flask 是单线程运行,若是在某个页面中执行了一些耗时的工做,那么程序就会在这里等待,没法响应其余的请求。也就是说,若是一个路由响应函数中有阻塞代码,那么其余用户没法访问这个 web 服务器,并且本身也打不开其余页面了。git

在一个路由中添加阻塞代码,以下所示:github

# app.py

from time import sleep
@app.route('/testsleep')
def test_sleep():
    sleep( 10 )
    return 'Hi, You wait for about 10 seconds, right?'

当打开 /testsleep 页面时,会发现浏览器一直在加载过程当中,再去打开 / 页面,发现这个页面也是在加载中。只有等到 /testsleep 页面加载完了,才会去加载 / 页面。web

在 flask 中使用 gevent

为了解决一个页面耗时致使全部页面都没法访问的问题。考虑使用 gevent 非阻塞的运行服务器程序。在引入 gevent 前,能够在程序最开始执行的位置引入猴子补丁 gevent.monkey,这能修改 python 默认的 IO 行为,让标准库变成 协做式(cooperative)的 API。注意引入 gevent 后,不能再用原来的方式启动咱们的 web 应用了:flask

# app.py
from gevent import monkey
monkey.patch_all()  # 打上猴子补丁

from flask import flask
...

if __name__ == '__main__':
    from gevent import pywsgi
    app.debug = True
    server = pywsgi.WSGIServer( ('127.0.0.1', 5000 ), app )
    server.serve_forever()

这个时候再去打开 /testsleep 页面,仍是要等待一些时间才会加载完页面,可是这个时候已经访问 / 页面将会当即加载完毕。api

启用调试模式和自动刷新模板

若是在某个页面中的代码有问题,会出现运行时错误,那么访问这个页面只能看到 Internal Server Error 的提示,没有了以前的调试窗口和错误信息。并且在上面的代码中,我已经将 app 的 debug 标志设为了真,然而并无什么用。为了启用调试模式,方便在开发时看到错误信息,咱们须要用到 werkzuge 提供的 DebuggedApplication浏览器

# app.py
if __name__ == '__main__':
    from werkzeug.debug import DebuggedApplication
    dapp = DebuggedApplication( app, evalex= True)
    server = pywsgi.WSGIServer( ( '127.0.0.1', 5000 ), dapp )
    server.serve_forever()

从新打开首页,能够看到熟悉的错误信息。服务器

若是你使用了模板,那么你可能已经注意到了,使用 gevent 后修改模板再次访问可能也不会看到页面上有相应的改动。那么你须要在修改 app 的配置,以便模板可以自动刷新,如下两种方式是等效的:app

app.jinja_env.auto_reload = True
app.config['TEMPLATES_AUTO_RELOAD'] = True

尝试自动从新加载

使用了 gevent 后,原有的一些功能都须要经过必定的配置以后才能够正常访问。可是有一个功能咱们仍然没有解决,那就是修改代码后 web 应用不会自动从新加载了。stackoverflowgist 提到的一种解决方法是使用 werkzeug 提供的 run_with_reloader,能够写出这样的代码:

# app.py

...

if __name__ == '__main__':
    ...

    from werkzeug.serving import run_with_reloader
    server = pywsgi.WSGIServer( ( '127.0.0.1', 5000 ), dapp )
    run_with_reloader( server ).serve_forever()

然而若是你这样作了就会发现一点用都没有,甚至连 web 应用都不能正常启动了。

按照这个思路来的还有这段代码提供的 示例,但这个示例是将 run_with_reloader 做为装饰器来使用,如下是该示例的代码:

import gevent.wsgi
import werkzeug.serving

@werkzeug.serving.run_with_reloader
def runServer():
    app.debug = True

    ws = gevent.wsgi.WSGIServer(('', 5000), app)
    ws.serve_forever()

然而这也没有什么做用。看一下 flask 的源代码能够发现,run_with_reloader 已经不是装饰器了。并且开发者提醒咱们不要使用下面的这个函数,这个 api 很明显已经被废弃了,flask 源代码以下:

def run_with_reloader(*args, **kwargs):
    # People keep using undocumented APIs.  Do not use this function
    # please, we do not guarantee that it continues working.
    from werkzeug._reloader import run_with_reloader
    return run_with_reloader(*args, **kwargs)

若是使用 gevent 做为 WSGI 的网关服务器,彷佛就无法使用自动加载应用的功能了。

实现自动从新加载

没有其余能够借鉴的方法了,好在以前在查看廖雪峰的 Python 教程时,给出了一个自动从新加载应用的示例,主要原理是利用 watchdog 提供的文件监听功能,在建立、修改文件时会触发相应的处理器,这样就能够实现自动从新加载功能。代码能够去廖雪峰的教程中查看。

以后的应用启动时就不能直接使用 python app.py 了。若是将自动加载的代码保存在同级的 monitor.py 文件中,咱们须要使用 python monitor.py app.py 启动应用。最终就能够自动热加载咱们的 web 应用了。

关于文件改动事件,以前我也写过一个相似的 JS 程序,原理相似,都是当文件改动时自动执行从新构建应用的命令。

相应的说明代码在 github 上能够查看。

References

  1. gevent monkey
  2. hot reload gevent wsgiserver
  3. gist
  4. code snippet
  5. 廖雪峰 Python 教程
相关文章
相关标签/搜索