flask 流式响应 RuntimeError: working outside of request context

2023-12-22 06:59

本文主要是介绍flask 流式响应 RuntimeError: working outside of request context,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1、问题

最近要实现这样一个功能:某个 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 =requestprint reqprint "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 = 0for url in urlList():i += 1resultStr = urlprint i, resultStryield i, resultStrprint reqprint "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 使用。

以上这两种办法都曾试过,但是由于理解上的偏差,导致一直未能成功。后来经过 坚实 同学的指点,才明白个中缘由,问题得以解决。

2、解决方案

(1)复制 request

将请求复制下来但不能直接 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.requestdef generateFunc():if new_request.method == '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 这种以下划线开头的变量属于私有变量,外部是不应该调用的,不过坚实同学暂时也没有找到其他能正式调用到它的方法 ,就先这么用着吧。

(2)构造全新 request

上面的这种写法: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 请求

(3)关于 @copy_current_request_context

这是官方宣称在 1.0 中实现的一个新特性,http://flask.pocoo.org/docs/api/#flask.copy_current_request_context 看说明应该可以更加优雅的解决上述问题,

但是试了下貌似不行,可能是组件间的兼容性问题。

(4)关于 Streaming with 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:

http://stackoverflow.com/questions/19755557/streaming-data-with-python-and-flask-raise-runtimeerror-working-outside-of-requ/20189866?noredirect=1#20189866

3、结论

(1)flask.request 和 streaming templates 兼容性不是很好,应该尽量不在 streaming templates 里调用 request,
把需要的值提前准备好,然后再传到 templates 里。这里也有人遇到同样的问题:

http://flask.pocoo.org/mailinglist/archive/2012/4/1/jinja2-stream-doesn-t-work/#8afda9ecd9682b16e8198a2f34e336fb

用 copy_current_request_context 没有效果应该也是上面这个原因。

(2)在文档语焉不详,同时 google 不到答案的时候,读源码或许是最后的选择,这也是一种能力吧。。。 - _ -

4、Refer:

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 地址:

https://github.com/anjianshi

http://segmentfault.com/u/anjianshi

5、最后附上完整的测试源码:

# -*- coding: utf-8 -*-
import sysreload(sys)
sys.setdefaultencoding('utf-8')
from flask import Flask, request, Responseapp = Flask(__name__)def stream_template(template_name, **context):# http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templatesapp.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.requestif new_request.method == '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="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>

6、推荐阅读:

[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

http://python.jobbole.com/87450/

转载于:https://my.oschina.net/leejun2005/blog/175364

这篇关于flask 流式响应 RuntimeError: working outside of request context的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/522993

相关文章

SpringBoot定制JSON响应数据的实现

《SpringBoot定制JSON响应数据的实现》本文主要介绍了SpringBoot定制JSON响应数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录前言一、如何使用@jsonView这个注解?二、应用场景三、实战案例注解方式编程方式总结 前言

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

简单的角色响应鼠标而移动

actor类 //处理移动距离,核心是找到角色坐标在世界坐标的向量的投影(x,y,z),然后在世界坐标中合成,此CC是在地面行走,所以Y轴投影始终置为0; using UnityEngine; using System.Collections; public class actor : MonoBehaviour { public float speed=0.1f; CharacterCo

Vue3上传图片报错:Current request is not a multipart request

当你看到错误 "Current request is not a multipart request" 时,这通常意味着你的服务器或后端代码期望接收一个 multipart/form-data 类型的请求,但实际上并没有收到这样的请求。在使用 <el-upload> 组件时,如果你已经设置了 http-request 属性来自定义上传行为,并且遇到了这个错误,可能是因为你在发送请求时没有正确地设置

使用http-request 属性替代action绑定上传URL

在 Element UI 的 <el-upload> 组件中,如果你需要为上传的 HTTP 请求添加自定义的请求头(例如,为了通过身份验证或满足服务器端的特定要求),你不能直接在 <el-upload> 组件的属性中设置这些请求头。但是,你可以通过 http-request 属性来自定义上传的行为,包括设置请求头。 http-request 属性允许你完全控制上传的行为,包括如何构建请求、发送请

context:component-scan使用说明!

<!-- 使用annotation 自动注册bean, 并保证@Required、@Autowired的属性被注入 --> <context:component-scan base-package="com.yuanls"/> 在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controll

Eclipse或MyEclipse中Java Working Set管理项目

随着学习JAVA的时间的越来越久,项目也越来越多,Eclipse或MyEclipse界面中显示一堆! 每次工作使用到的项目肯定不会太多...... 每次从这么大数量的工程当中找到自己要使用的, 必须大规模的滚动滚动条...... 图片一   Project Explorer中:    图片二:Package Explorer中: 这样就好找很多了,分类放!

React的context学习总结

context是干什么的?为什么会存在这么一个东西? context字面意思是上下文,在react中存在是为了解决深层次组件传值困难的问题 这里涉及到组件的传值问题,大体商说分三总:兄弟间传值(通过父组件),父往子传值(通过props),子往父传(props函数回调),这是基础的传值问题,但是如果组件嵌套的太深,那么传值就变的非常麻烦,为了解决这样的问题才产生了context  这是cont