Python从FTP服务器流传输到Flask服务器以进行下载

时间:2018-06-25 13:08:25

标签: python flask ftp ftplib

我有一个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呢?

1 个答案:

答案 0 :(得分:1)

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

由于遇到了同样的问题,我为此做了一个快速总结。这种方法似乎有效。

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()在设置完成之前。

如果有人能想到一种更优雅的方式,将会对此产生兴趣。