前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >flask 流式响应 RuntimeError: working outside of request context

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

作者头像
用户1177713
发布于 2018-02-24 07:51:57
发布于 2018-02-24 07:51:57
4K00
代码可运行
举报
文章被收录于专栏:数据之美数据之美
运行总次数:0
代码可运行

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 的交互过程了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 上下文:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 取了个别名,它们是共享引用。正确的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 这种以下划线开头的变量属于私有变量,外部是不应该调用的,不过坚实同学暂时也没有找到其他能正式调用到它的方法 ,就先这么用着吧。

(2)构造全新 request

上面的这种写法:with app.test_request_context('/username', method='GET'):

之所以不可以是因为 app.test_request_context 创建的是一个全新的 request,它包含的 url, method, headers, form 值都是要在创建时自定义的,它不会把原来的 request 里的数据带进来,需要自己传进去,类似这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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、最后附上完整的测试源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# -*- 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)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!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/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Flask
1.Flask实例配置 app.config.form_object("setting.FlaskSetting") app.DEBUG = True 开启Debug模式,该完代码不用手动重启 app.SECRET_KEY = "xxxxx" 开启session必备参数
py3study
2020/01/19
1.8K0
Flask
Python-基于flask的接口框架
Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务。本文参考自Flask官方文档,大部分代码引用自官方文档。
用户9925864
2022/07/27
4550
Flask(7)- request 对象
服务端收到将客户端发送的数据后,封装形成一个请求对象,在 Flask 中,请求对象是一个模块变量 flask.request
小菠萝测试笔记
2021/07/14
1K0
Flask(7)- request 对象
Flask(请求和响应 五)
使用ajax会返回X-Requested-With: XMLHttpRequest可以在XHR中找到
zx钟
2020/06/19
5790
python中flask 常见问题
本文出自https://blog.csdn.net/qq_33020901/article/details/69802445
py3study
2020/01/13
1.7K0
大白话说Python+Flask入门(二)
笔者技术真的很一般,也许是只靠着笨鸟先飞的这种傻瓜坚持,才能侥幸的在互联网行业生存下来吧!
软件测试君
2023/11/22
2360
大白话说Python+Flask入门(二)
Flask之WTForms
WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。
人生不如戏
2018/08/01
8910
Flask框架web开发:零基础入门 原
Flask框架是Python开发的一个基于Werkzeug和Jinja 2的web开发微框架,它的优势就是极其简洁,但又非常灵活,而且容易学习和应用。因此Flask框架是Python新手快速开始web开发最好的选择,此外,使用Flask框架的另一个好处在于你可以非常轻松地将基于Python的机器学习算法或数据分析算法集成到web应用中。
笔阁
2018/12/25
1.9K1
Flask 实现文件上传下载
Flask 针对文件的上传下载相关代码片段,多种方法,包括限制文件格式,大小等。 实现图片文件上传 # name: 简单的实现文件上传任务. import os from flask import Flask, request, url_for, send_from_directory from werkzeug import secure_filename ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) app = Flask(__nam
王瑞MVP
2022/12/28
1.1K0
Flask 学习-5.请求对象Request
前言 在 Flask 中 由全局对象 request 来提供请求信息。 Request 请求对象 首先,您必须从 flask 模块导入请求对象: from flask import request 通过使用 method 属性可以操作当前请求方法,通过使用 form 属性处理表单数据(在 POST 或者 PUT 请求 中传输的数据)。 以下是使用上述两个属性的例子: from flask import Flask from flask import render_template from flask i
上海-悠悠
2022/08/26
7460
Flask 学习-5.请求对象Request
web开发框架Flask学习一
flask框架 用Python做Web开发的三大框架特点 Django 主要特点是大而全,集成了很多的组件,例如:Admin Form Model等,不管你用不用的到,他都会为 你提供,通常用于大型Web应用,由于内部组件足够强大,所以使用Django可以做到一气呵成,    Django的优点是大而全,缺点也就露出来,这么多的资源一次性全部加载,肯定会造成cpu资源的浪费 flask
py3study
2020/01/19
7430
Flask 快速入门
Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务。本文参考自Flask官方文档,大部分代码引用自官方文档。 安装Flask 首先我们来安装F
乐百川
2018/01/09
1.4K0
Flask 快速入门
【Flask】大型项目中对于url_for() 的使用以及请求数据上传文件的开发实例
url_For()是flask框架提供的函数。第一个参数可以作为表示路线的端点传入。它主要用于生成URL,避免开发人员手写URL。 使用url_ for()生成的url是相对路径。一些开发人员更喜欢用绝对路径定义文件路径。(这是非常不友好和不灵活的!) 所以也许你仍然认为它是抽象的。让我们用一个小演示来演示:
上进小菜猪
2022/12/13
6380
【Flask】大型项目中对于url_for() 的使用以及请求数据上传文件的开发实例
Flask基础入门学习笔记-1
描述:Flask 官方介绍Web Develoment one drop at a time,实际上它是一个基于Python开发的Web轻量级框架; 通过Flask和各种插件的配合使用,以新的框架实现Web前后端联合开发。
全栈工程师修炼指南
2020/10/23
1.6K0
Flask基础入门学习笔记-1
Flask 框架基础知识笔记
内置过滤器: tojson配合js使用,注意这里要避免HTML自动转义,所以加上safe过滤器
王瑞MVP
2022/12/28
2.4K0
Flask 框架:运用WTForms实现用户注册
WTForms 是用于web开发的灵活的表单验证和呈现库,它可以与您选择的任何web框架和模板引擎一起工作,并支持数据验证、CSRF保护、国际化等,运用WTForms框架并配合Flask可实现一个带有基本表单验证功能的用户注册与登录页面,经过美化的页面可以直接应用到项目中。
王瑞MVP
2022/12/28
5820
Flask 框架:运用WTForms实现用户注册
day114-Flask启动&Response内容&Request内容&模板语言
1.Flask启动(启动即运行) from flask import Flask # 指定一个 app app = Flask(__name__) @app.route('/') # 指定路径 def hello_world(): # 返回一个字符串 return 'Hello World ' if __name__ == '__main__': app.run(debug=True, host="0.0.0.0", port=9527) 2.Flask的基础respo
少年包青菜
2020/04/18
5170
flask web开发实战 入门 pdf_常用的web开发框架
Flask是一个用Python编写的Web应用程序框架。Flask基于Werkzeug(WSGI工具包)和Jinja2模板引擎。
全栈程序员站长
2022/09/27
7.3K0
flask web开发实战 入门 pdf_常用的web开发框架
flask 教程_python flask快速入门与进阶
Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。
全栈程序员站长
2022/09/20
2K0
看完这篇文章还能不懂Flask这种Web框架吗?
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
IT派
2018/07/30
5110
相关推荐
Flask
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验