2019独角兽企业重金招聘Python工程师标准>>>
最近要实现这样一个功能:某个 cgi 处理会很耗时,需要把处理的结果实时的反馈给前端,而不能等到后台全完成了再咔一下全扔前端,那样的用户体验谁都没法接受。
web 框架选的 flask,这个比较轻量级,看了下官方文档,恰好有个叫 Streaming from Templates 的功能:
可以满足需求,它以 generate yield 为基础,流式的返回数据到前端。看了下官方的例子貌似很简单,一笔带过,我又搜了下 stackoverflow,上面有个老外给了个更加详尽的例子:Streaming data with Python and Flask
文中的答案没有前后端的数据交互过程,那我就根据自己的需求加个 http 的交互过程了:
ute('/username', methods=['GET', 'POST'])
def index():req =requestprint reqprint "111------------" + hod + "n"def ggg1(req):print req # the req not my pass into the print "444------------" + hod + "n"if hod == 'POST':if request.form['username']:urlList = request.form['username'].splitlines()i = 0for url in urlList():i += 1resultStr = urlprint i, resultStryield i, resultStrprint reqprint "222------------" + hod + "n"return Response(stream_template('index.html', data=ggg1(req)))
好吧,这么一加,噩梦就开始了。。。奇葩的问题出现了:
要么第 5 行和第 8 行不等,要么就是第 9 行报错:
hod == 'POST': # RuntimeError: working outside of request context
继续在 stackoverflow 上搜索,发现有人遇到了同样的问题,得到的建议是在调用前声明一个 request 上下文:
with st_request_context('/username', method='GET'):index()
折腾了老半天,还是依旧报错:RuntimeError: working outside of request context
看起来似乎是在进入迭代器以前,原本的 request 的生命周期就已经结束了,因此就没办法再调用了。
那么要解决就有 2 种办法了:
(1)在进入 generationFunc 前将请求复制一份保存下来以供 generationFunc 调用。
(2)利用 st_request_context 创建的是一个全新的 request,将数据传给 generationFunc 使用。
以上这两种办法都曾试过,但是由于理解上的偏差,导致一直未能成功。后来经过 坚实 同学的指点,才明白个中缘由,问题得以解决。
将请求复制下来但不能直接 req = request 这种形式,这只是给 request 取了个别名,它们是共享引用。正确的代码如下:
from import _request_ctx_stack
global new_request
ute('/')
ute('/demo', methods=['POST'])
def index():ctx = _request_p.copy()new_request = questdef generateFunc():if hod == 'POST':if new_request.form['digitValue']:num = int(new_request.form['digitValue'])i = 0for n in xrange(num):i += 1print "%s:t%s" % (i, n)yield i, nreturn Response(stream_template('index.html', data=generateFunc()))
PS: 其实像 _request_ctx_stack 这种以下划线开头的变量属于私有变量,外部是不应该调用的,不过坚实同学暂时也没有找到其他能正式调用到它的方法 ,就先这么用着吧。
上面的这种写法:st_request_context('/username', method='GET'):
之所以不可以是因为 st_request_context 创建的是一个全新的 request,它包含的 url, method, headers, form 值都是要在创建时自定义的,它不会把原来的 request 里的数据带进来,需要自己传进去,类似这样:
with st_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 中实现的一个新特性,.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, Responseute('/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#20189866
(1quest 和 streaming templates 兼容性不是很好,应该尽量不在 streaming templates 里调用 request,
把需要的值提前准备好,然后再传到 templates 里。这里也有人遇到同样的问题:
用 copy_current_request_context 没有效果应该也是上面这个原因。
(2)在文档语焉不详,同时 google 不到答案的时候,读源码或许是最后的选择,这也是一种能力吧。。。 - _ -
/
/
附坚实同学的 github 与 sf 地址:
# -*- coding: utf-8 -*-
import sysreload(sys)
sys.setdefaultencoding('utf-8')
from flask import Flask, request, Responseapp = Flask(__name__)def stream_template(template_name, **context):# .update_template_context(context)t = app._template(template_name)rv = t.stream(context)# uncomment if you don't need immediate reaction##rv.enable_buffering(5)return rvute('/')
ute('/demo', methods=['POST'])
def index():with st_request_context('/demo', method='POST', data=request.form) as new_context:def generateFunc():new_request = questif hod == 'POST':if new_request.form['digitValue']:num = int(new_request.form['digitValue'])i = 0for n in xrange(num):i += 1print "%s:t%s" % (i, n)yield i, nreturn 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=".7.0/html5shiv.js"></script><script src=".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=".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=".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实现视频数据流传输
/
[2] Video Streaming with Flask
[3] Flask 的 Context 机制
/
[4] flask 源码解析:session
/
转载于:
本文发布于:2024-02-01 19:50:04,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170678820439042.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |