在 google App Engine 柔性环境中使用 stream_with_context 在 Flask 中加载栏

Loading bar in flask using stream_with_context on google app engine flexible environment

用例 - 一些用户数据正在后端(flask)加载,进度通过加载栏显示在前端。后端有一个生成器,它加载数据并保持 产生 进度(此生成器作为使用 stream_with_context 的响应返回)。前端使用 javascript EventSource 对象查询 flask 视图。

代码:

@app.route("/progress", methods=['GET'])
def progress():
    gen = get_user_data()
    return Response(stream_with_context(gen), mimetype= 'text/event-stream')

def get_user_data():
    n = 100 (number of data points to be loaded)
    for i in range(1,n+1):
        #load data
        yield "data:" + str((float(i)/(n))*100) + "\n\n"
    yield "data:" + "close" + "\n\n"

这在我的本地环境中运行良好。但是,当我在 google App Engine 灵活环境 上部署它时,加载条直接从 0 变为 100。也就是说,前端不是每次都获取更新生成器产生,我一次获得所有 EventSource 消息(当生成器完成执行时)。

我的app.yaml:

runtime: python
env: flex
entrypoint: gunicorn --timeout 240 -b :$PORT app:app

runtime_config:
  python_version: 2

manual_scaling:
  instances: 1

resources:
  cpu: 1
  memory_gb: 0.5
  disk_size_gb: 10

关于如何让它在 google 应用程序引擎上工作的任何想法?

"An EventSource instance opens a persistent connection to an HTTP server", 根据this documentation. This solution is not going to work in App Engine according to the explanation provided here:

You could attempt to declare "Content-Type: text/event-stream" on your own vanilla App Engine handler, and use an EventSource

https://developer.mozilla.org/en-US/docs/Web/API/EventSource

object in the browser to initiate a keep-alive connection. The problem is, App Engine waits for the handler on your app to return fully before flushing the buffer and sending the response data. You can find this documented here:

https://cloud.google.com/appengine/docs/java/requests#Java_Responses for java https://cloud.google.com/appengine/docs/python/requests#Python_Responses for python https://cloud.google.com/appengine/docs/php/requests#PHP_Responses for php https://cloud.google.com/appengine/docs/go/requests#Go_Responses for go.

What this means in practice is that your stream will not be "kept-alive" and will close each time one response is sent. Or, if you implement your server-sent event code server-side as most people do, it will buffer up all of its responses and finally send them all only when it terminates.

目前有几个复杂的解决方法:

  • Using Pusher: "Pusher is a hosted API for sending real-time, bi-directional messages via WebSockets to apps and other INternet-connected devices." 这不是官方产品文档,但其作者是 Google 员工。
  • 如果你使用Firebase:"You can use App Engine in conjunction with the Firebase Realtime Database to send immediate updates to browser and mobile clients without a persistent streaming connection to the server or long polling. "

根据 this issuetracker 中的消息 #231,一种更简单的方法很快就会可用。 Flex WebSockets Beta 发布即将推出,但对于标准环境,它是 "at least a year away"。如果您想获得有关评论和更新的自动通知,请在问题跟踪器 post 中加注星标。