在 Flask 应用中使用 gevent

普通的 flask 应用

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

# 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 检测到文件变动后就会自动重新加载我们的应用程序。

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

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

# 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 页面加载完了,才会去加载 / 页面。

在 flask 中使用 gevent

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

# 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 页面,还是要等待一些时间才会加载完页面,但是这个时候已经访问 / 页面将会立即加载完毕。

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

如果在某个页面中的代码有问题,会出现运行时错误,那么访问这个页面只能看到 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.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 教程

在 Flask 应用中使用 gevent的更多相关文章

  1. Flask 框架中 上下文基础理念,包括cookie,session存储方法,requset属性,current_app模块和g模块

    Flask中上下文,分为请求上下文和应用上下文.既状态留存 ,就是把变量存在某一个地方可以调用 请求上下文:实际就是request和session用法理念,既都是可以存储东西. 应用上下文:既变量共享 ...

  2. Flask项目中整合各组件

    一.介绍 主要介绍flask_sqlalchemy.flask_script.flask_migrate这三个组件该如何整合到flask项目中,以及如何使用. # 安装组件 pip3 install ...

  3. Flask项目中使用mysql数据库启动项目是发出警告

    Flask项目中使用mysql数据库启动项目是发出警告: Warning: (1366, "Incorrect string value: '\xD6\xD0\xB9\xFA\xB1\xEA ...

  4. flask SQLAlchemy中一对多的关系实现

    SQLAlchemy是Python中比较优秀的orm框架,在SQLAlchemy中定义了多种数据库表的对应关系, 其中一对多是一种比较常见的关系.利用flask sqlalchemy实现一对多的关系如 ...

  5. uwsgi+flask环境中安装matplotlib

    uwsgi+flask的python有自身的virtual environment,可以通过如下命令进入 . venv/bin/activate 虽然通过sudo apt-get install py ...

  6. flask页面中Head标签内容为空问题

    在使用flask时遇到点问题,以前还没有注意到. 生成页面的时候使用的是模板继承方式,当添加meta标题的时候,本来是添加的base.html模板中的head标签中,但是生成页面后,head中的内容却 ...

  7. werkzeug(flask)中的local,localstack,localproxy探究

    1.关于local python中有threading local处理方式,在多线程环境中将变量按照线程id区分 由于协程在Python web中广泛使用,所以threading local不再满足需 ...

  8. python flask route中装饰器的使用

    问题:route中的装饰器为什么感觉和平时使用的不太一样,装饰器带参数和不太参数有什么区别?被修饰的函数带参数和不带参数有什么区别? 测试1:装饰器不带参数,被修饰的函数也不带参数. def log( ...

  9. nginx的rewrite ,如何在flask项目中获取重写前的url

    1. 在flask配一个重写到哪的路由,假设是/rewite/,然后到nginx的配置文件写重写规则,我这里重写全部的请求,接着测试能否重写成功 1. 添加一个路由 配置重写规则 测试成功 2.接下来 ...

随机推荐

  1. Struts2学习第一天--Struts2的概述、Struts2的入门、Struts2常见的配置、Struts2的Action的编写

    action的name要与访问路径对应.hello.action. 加到tomcat启动 访问:http://localhost:8080/struts2-1/demo1/demo1.jsp 改为su ...

  2. linux下关闭防火墙命令

    今天使用linux虚拟机搭建jenkins,但是在虚拟机内部使用浏览器可以访问jenkins主页,在物理机上却无法访问jenkins主页,查找原因后是因为linux虚拟机没有关闭防火墙,关闭防火墙后, ...

  3. Java面向对象之关键字super 入门实例

    一.基础概念 (一)super关键字 super关键字的用法和this相似.this代表的是当前对象.super代表的是父类中内存空间. 子父类中是不会出现同名属性的情况. (二)继承中.成员变量问题 ...

  4. Android Gradle 隐形依赖的奇怪案例

    相信 Android 开发者都有在 Android Studio 中升级 compileSdkVersion 的经历,这个时候如果你使用了 support 包,并同时升级,那么可能会出现一个错误提示. ...

  5. Python正则表达式匹配日期与时间

    #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Randy' import re from datetime import da ...

  6. python wraps装饰器

    这是一个很有用的装饰器.看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字 wrapper,如果你希望使用反射,可能会导致意外的结果.这 ...

  7. 洛谷 P2330 [SCOI2005]繁忙的都市(最小生成树)

    嗯... 题目链接:https://www.luogu.org/problemnew/show/P2330 这道题的问法也实在是太模板了吧: 1.改造的道路越少越好 2.能够把所有的交叉路口直接或间接 ...

  8. java 常用模块代码

    1.文件的读写操作 (1)进行读操作 import java.io.BufferedReader; import java.io.FileNotFoundException; import java. ...

  9. codeforces 703D Mishka and Interesting sum 偶数亦或 离线+前缀树状数组

    题目传送门 题目大意:给出n个数字,m次区间询问,每一次区间询问都是询问 l 到 r 之间出现次数为偶数的数 的亦或和. 思路:偶数个相同数字亦或得到0,奇数个亦或得到本身,那么如果把一段区间暴力亦或 ...

  10. HDU6393(LCA + RMQ + 树状数组) n边图,两点最短距离 , 修改边权

    这道题的进阶版本 进阶版本 题意: 一个n个点,n条边的图,2中操作,1是将某条边的权值更改,2是询问两点的最短距离. 题解: 由于n个点,n条边,所以是树加一个环,将环上的边随意取出一条,就是1颗树 ...