流内容

有时候你会需要把大量数据传送到客户端,不在内存中保存这些数据。当你想把 运行中产生的数据不经过文件系统,而是直接发送给客户端时,应当怎么做呢?

答案是使用生成器和直接响应。

基本用法

下面是一个在运行中产生大量 CSV 数据的基本视图函数。其技巧是调用一个内联 函数生成数据,把这个函数传递给一个响应对象:

  1. @app.route('/large.csv')
  2. def generate_large_csv():
  3. def generate():
  4. for row in iter_all_rows():
  5. yield f"{','.join(row)}\n"
  6. return app.response_class(generate(), mimetype='text/csv')

每个 yield 表达式被直接传送给浏览器。注意,有一些 WSGI 中间件可能会 打断流内容,因此在使用分析器或者其他工具的调试环境中要小心一些。

模板中的流内容

Jinja2 模板引擎也支持分片渲染模板。这个功能不是直接被 Flask 支持的,因 为它太特殊了,但是你可以方便地自已来做:

  1. def stream_template(template_name, **context):
  2. app.update_template_context(context)
  3. t = app.jinja_env.get_template(template_name)
  4. rv = t.stream(context)
  5. rv.enable_buffering(5)
  6. return rv
  7. @app.route('/my-large-page.html')
  8. def render_large_template():
  9. rows = iter_all_rows()
  10. return app.response_class(stream_template('the_template.html',
  11. rows=rows))

上例的技巧是从 Jinja2 环境中获得应用的模板对象,并调用 stream() 来代替 render() ,返回一个流对象来代替一个字符串。由于 我们绕过了 Flask 的模板渲染函数使用了模板对象本身,因此我们需要调用 update_template_context() ,以确保更新被渲染的内容。 这样,模板遍历流内容。由于每次产生内容后,服务器都会把内容发送给客户 端,因此可能需要缓存来保存内容。我们使用了 rv.enable_buffering(size) 来进行缓存。 5 是一个比较明智的缺省 值。

情境中的流内容

Changelog

New in version 0.9.

注意,当你生成流内容时,请求情境已经在函数执行时消失了。 Flask 0.9 为你 提供了一点帮助,让你可以在生成器运行期间保持请求情境:

  1. from flask import stream_with_context, request
  2. @app.route('/stream')
  3. def streamed_response():
  4. def generate():
  5. yield 'Hello '
  6. yield request.args['name']
  7. yield '!'
  8. return app.response_class(stream_with_context(generate()))

如果没有使用 stream_with_context() 函数,那么就会引发 RuntimeError 错误。