在 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 教程
相關文章
相關標籤/搜索