Flask:通过写入客户端来传输数据?

时间:2014-05-06 01:19:44

标签: python flask werkzeug

我有现有的代码将数据序列化为类似文件的对象:

def some_serialization_function(file):
    file.write(...)

在Flask中,我希望能够将序列化数据直接发送到客户端, 不先在内存中缓冲它。

我查看了来自werkzeug的ResponseStreamMixin,但我认为它可以在没有缓冲的情况下工作:

class StreamResponse(flask.Response, werkzeug.wrappers.ResponseStreamMixin):
   pass

@app.route("/data")
def get_data():
   r = StreamResponse()
   some_serialization_function(r.stream) # everything is buffered into memory
   return r # buffered data is sent after return

我发现的流数据的所有示例都是基于生成器,它们在相反的方向上工作(即数据是"从发生器拉出#34;而不是"推出"通过a写电话),所以我想,有一种方法可以#34;写"直接给Flask的客户?

编辑 - 更清楚:我正在寻找一种方法来提供由" some_serialization_function(...)"生成的数据。 (我不能轻易改变)没有使用该函数的内存/ IO开销首先将所有数据写入缓冲区/文件。

(我怀疑临时文件将是最终的方式,因为与通过网络实际发送数据的开销相比,IO开销不会很大。此外,我主要担心的是内存开销)。

2 个答案:

答案 0 :(得分:4)

您可以创建一个特殊的类文件对象,该对象将流式传输到客户端的生成器。这是一个快速的&使用队列的脏实现:

from queue import Queue

class StreamWriter(object):
    def __init__(self):
        self.queue = Queue()

    def write(self, str):
        self.queue.put(str)

    def read(self):
        str = self.queue.get()
        self.queue.task_done()
        if str == '~':
            return None
        return str

    def close(self):
        self.write('~')  # indicate EOF

这只不过是pub-sub类型的队列。 read()方法将阻塞,直到在另一个线程中写入内容。

现在您可以使用生成器传输响应。以下示例显示了将序列化函数作为参数的生成器。序列化函数在后台线程中执行,并接收类文件对象作为参数。

def generate_response(serialize):
    file = StreamWriter()
    def serialize_task():
        serialize(file)
        file.close()
    threading.Thread(target=serialize_task).start()
    while True:
        chunk = file.read()
        if chunk is None:
            break
        yield chunk

我希望这有帮助!

答案 1 :(得分:-1)

如果我理解你,你想要

  • 提供数据流的Flask网络应用
  • 客户端逐件获取数据,而不是一个大块
  • Flask网络应用程序处于控制状态,从而启动write应用程序。

我认为,由于某人必须控制流量,因此无法完成此操作。如果是网络应用,则客户端正在读取数据。

另一方面,如果您希望阻止缓存整个内容在Web应用程序上提供给客户端,您可以逐个读取Web服务器上的数据并逐个产生。

逐个服务器提供内容

from flask import Flask, Response, request
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/loop')
def loop():
    def generate():
        yield "Hello"
        yield "World"
    return Response(generate())

@app.route('/longloop/<int:rows>')
def longloop(rows):
    def generate(rows):
        for i in xrange(rows):
            yield "{i}: Hello World".format(i=i)
    return Response(generate(rows))

if __name__ == '__main__':
    app.run(debug=True)

诀窍是使用Response对象和生成器生成输出。

如果您访问http://localhost:5000/longloop/100,您将收到100个问候语。

使用curl从命令行尝试此操作,并更好地将输出重定向到/dev/null

$ curl -X GET http://localhost:5000/longloop/120000000 > /dev/null                                                                                                                              
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  538M    0  538M    0     0  1056k      0 --:--:--  0:08:41 --:--:-- 1079k

正如我们所看到的,脚本现在运行超过8分钟,烧瓶app消耗的内存大致相同,在我的情况下,它保持在总RAM的0.4%。