【问题标题】:Python stream from FTP server to Flask server for downloading从 FTP 服务器到 Flask 服务器的 Python 流以供下载
【发布时间】:2018-06-25 13:08:25
【问题描述】:

我有一个 Python Flask 应用程序,它获取从远程 FTP 服务器下载文件的请求。我使用BytesIO 来保存使用retrbinary 从FTP 服务器下载的文件的内容:

import os

from flask import Flask, request, send_file
from ftplib import FTP
from io import BytesIO

app = Flask(__name__)

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

@app.route('/download_content', methods=['GET'])
def download_content():
    filepath = request.args.get("filepath").strip()
    f = FTP(my_server)
    f.login(my_username, my_password)
    b = BytesIO()
    f.retrbinary("RETR " + filepath, b.write)
    b.seek(0)
    return send_file(b, attachment_filename=os.path.basename(filepath))

app.run("localhost", port=8080)

这里的问题是,当download_content路由被命中时,文件的内容首先进入BytesIO对象,然后发送到前端进行下载。

在从 FTP 服务器下载文件时,如何将文件流式传输到前端?我迫不及待地等待文件完全下载到 BytesIO 对象中,然后执行 send_file,因为这可能既是内存效率低下的,也是更耗时的。

我已经读过 Flask 的 send_file 接受 generator 对象,但我怎样才能将 BytesIO 对象 yield 以块的形式变为 send_file

【问题讨论】:

    标签: python flask ftp ftplib


    【解决方案1】:

    您似乎需要设置一个工作线程来管理来自retrbinary 的下载

    因为我们遇到了同样的问题,所以我对此做了一个快速的 Gist。这个方法似乎行得通。

    https://gist.github.com/Richard-Mathie/ffecf414553f8ca4c56eb5b06e791b6f

    class FTPDownloader(object):
      def __init__(self, host, user, password, timeout=0.01):
        self.ftp = FTP(host)
        self.ftp.login(user, password)
        self.timeout = timeout
    
      def getBytes(self, filename):
        print("getBytes")
        self.ftp.retrbinary("RETR {}".format(filename) , self.bytes.put)
        self.bytes.join()   # wait for all blocks in the queue to be processed
        self.finished.set() # mark streaming as finished
    
      def sendBytes(self):
        while not self.finished.is_set():
          try:
            yield self.bytes.get(timeout=self.timeout)
              self.bytes.task_done()
          except Empty:
            self.finished.wait(self.timeout)
        self.worker.join()
    
      def download(self, filename):
        self.bytes = Queue()
        self.finished = Event()
        self.worker = Thread(target=self.getBytes, args=(filename,))
        self.worker.start()
        return self.sendBytes()
    

    可能应该添加一些超时和逻辑来处理连接超时等,但这是基本形式。

    说明

    队列不保证工作进程getBytes 在它们为空时已经完成,因此您必须有一个信号量/事件来指示生成器sendBytes 当工作完成时。但是我必须等待队列中的所有块首先被处理,因此在设置完成之前self.bytes.join()

    如果有人能想到更优雅的方式来做这件事,我很感兴趣。

    【讨论】:

      猜你喜欢
      • 2012-07-30
      • 1970-01-01
      • 2014-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多