最近要实现这样一个功能:某个 cgi 处理会很耗时,需要把处理的结果实时的反馈给前端,而不能等到后台全完成了再咔一下全扔前端,那样的用户体验谁都没法接受。
web 框架选的 flask,这个比较轻量级,看了下官方文档,恰好有个叫 Streaming from Templates 的功能:
http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templates
可以满足需求,它以 generate yield 为基础,流式的返回数据到前端。看了下官方的例子貌似很简单,一笔带过,我又搜了下 stackoverflow,上面有个老外给了个更加详尽的例子:Streaming data with Python and Flask
http://stackoverflow.com/questions/13386681/streaming-data-with-python-and-flask
文中的答案没有前后端的数据交互过程,那我就根据自己的需求加个 http 的交互过程了:
@app.route('/username', methods=['GET', 'POST'])
def index():
req =request
print req
print "111------------" + req.method + "\n"
def ggg1(req):
print req # the req not my pass into the req....
print "444------------" + req.method + "\n"
if req.method == 'POST':
if request.form['username']:
urlList = request.form['username'].splitlines()
i = 0
for url in urlList():
i += 1
resultStr = url
print i, resultStr
yield i, resultStr
print req
print "222------------" + req.method + "\n"
return Response(stream_template('index.html', data=ggg1(req)))
好吧,这么一加,噩梦就开始了。。。奇葩的问题出现了:
要么第 5 行和第 8 行不等,要么就是第 9 行报错:
if request.method == 'POST': # RuntimeError: working outside of request context
继续在 stackoverflow 上搜索,发现有人遇到了同样的问题,得到的建议是在调用前声明一个 request 上下文:
with app.test_request_context('/username', method='GET'):
index()
折腾了老半天,还是依旧报错:RuntimeError: working outside of request context
看起来似乎是在进入迭代器以前,原本的 request 的生命周期就已经结束了,因此就没办法再调用了。
那么要解决就有 2 种办法了:
(1)在进入 generationFunc 前将请求复制一份保存下来以供 generationFunc 调用。
(2)利用 app.test_request_context 创建的是一个全新的 request,将数据传给 generationFunc 使用。
以上这两种办法都曾试过,但是由于理解上的偏差,导致一直未能成功。后来经过 坚实 同学的指点,才明白个中缘由,问题得以解决。
将请求复制下来但不能直接 req = request 这种形式,这只是给 request 取了个别名,它们是共享引用。正确的代码如下:
from flask.ctx import _request_ctx_stack
global new_request
@app.route('/')
@app.route('/demo', methods=['POST'])
def index():
ctx = _request_ctx_stack.top.copy()
new_request = ctx.request
def generateFunc():
if new_request.method == 'POST':
if new_request.form['digitValue']:
num = int(new_request.form['digitValue'])
i = 0
for n in xrange(num):
i += 1
print "%s:\t%s" % (i, n)
yield i, n
return Response(stream_template('index.html', data=generateFunc()))
PS: 其实像 _request_ctx_stack 这种以下划线开头的变量属于私有变量,外部是不应该调用的,不过坚实同学暂时也没有找到其他能正式调用到它的方法 ,就先这么用着吧。
上面的这种写法:with app.test_request_context('/username', method='GET'):
之所以不可以是因为 app.test_request_context 创建的是一个全新的 request,它包含的 url, method, headers, form 值都是要在创建时自定义的,它不会把原来的 request 里的数据带进来,需要自己传进去,类似这样:
with app.test_request_context('/demo', method='POST', data=request.form) as new_context:
def generateFunc():
PS: test_request_context 应该是做单元测试用的,用来模仿用户发起的 HTTP 请求。 它做的事,和你通过浏览器提交一个表单或访问某个网页是差不多的。 例如你传给它 url='xxx'、method='post' 等等参数就是告诉它:向 xxx 发起一个 http 请求
这是官方宣称在 1.0 中实现的一个新特性,http://flask.pocoo.org/docs/api/#flask.copy_current_request_context 看说明应该可以更加优雅的解决上述问题,
但是试了下貌似不行,可能是组件间的兼容性问题。
New in version 0.9. Note that when you stream data, the request context is already gone the moment the function executes. Flask 0.9 provides you with a helper that can keep the request context around during the execution of the generator:
from flask import stream_with_context, request, Response
@app.route('/stream')
def streamed_response():
def generate():
yield 'Hello '
yield request.args['name']
yield '!'
return Response(stream_with_context(generate()))
Without the stream_with_context() function you would get a RuntimeError at that point.
REF:
(1)flask.request 和 streaming templates 兼容性不是很好,应该尽量不在 streaming templates 里调用 request, 把需要的值提前准备好,然后再传到 templates 里。这里也有人遇到同样的问题:
用 copy_current_request_context 没有效果应该也是上面这个原因。
(2)在文档语焉不详,同时 google 不到答案的时候,读源码或许是最后的选择,这也是一种能力吧。。。 - _ -
http://stackoverflow.com/questions/13386681/streaming-data-with-python-and-flask http://flask.pocoo.org/docs/patterns/streaming/ http://stackoverflow.com/questions/8224333/scrolling-log-file-tail-f-animation-using-javascript http://jsfiddle.net/manuel/zejCD/1/
附坚实同学的 github 与 sf 地址:
http://segmentfault.com/u/anjianshi
# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
from flask import Flask, request, Response
app = Flask(__name__)
def stream_template(template_name, **context):
# http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templates
app.update_template_context(context)
t = app.jinja_env.get_template(template_name)
rv = t.stream(context)
# uncomment if you don't need immediate reaction
##rv.enable_buffering(5)
return rv
@app.route('/')
@app.route('/demo', methods=['POST'])
def index():
with app.test_request_context('/demo', method='POST', data=request.form) as new_context:
def generateFunc():
new_request = new_context.request
if new_request.method == 'POST':
if new_request.form['digitValue']:
num = int(new_request.form['digitValue'])
i = 0
for n in xrange(num):
i += 1
print "%s:\t%s" % (i, n)
yield i, n
return Response(stream_template('index.html', data=generateFunc()))
if __name__ == "__main__":
app.run(host='localhost', port=8888, debug=True)
<!DOCTYPE html>
<html>
<head>
<title>Bootstrap 101 Template</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap -->
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body>
<style>
#data {
border: 1px solid blue;
height: 500px;
width: 500px;
overflow: hidden;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
function tailScroll() {
var height = $("#data").get(0).scrollHeight;
$("#data").animate({
scrollTop: height
}, 5);
}
</script>
<form role="form" action="/demo" method="POST">
<textarea class="form-control" rows="1" name="digitValue"></textarea>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<div id="data" style="position:relative;height:400px; overflow-x:auto;overflow-y:auto">nothing received yet</div>
{% for i, resultStr in data: %}
<script>
$("<div />").text("{{ i }}:\t{{ resultStr }}").appendTo("#data")
tailScroll();
</script>
{% endfor %}
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="/static/dist/js/bootstrap.min.js"></script>
</body>
</html>
[1] 用Flask实现视频数据流传输
http://python.jobbole.com/80994/
https://github.com/miguelgrinberg/flask-video-streaming
[2] Video Streaming with Flask
http://blog.miguelgrinberg.com/post/video-streaming-with-flask
[3] Flask 的 Context 机制
https://blog.tonyseek.com/post/the-context-mechanism-of-flask/
[4] flask 源码解析:session
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有