我编写了一个小的Flask应用程序,以从Google云端硬盘下载文件。
@app.route("/downloadFile/<id>")
def downloadFile(id):
ioBytes, name, mime = gdrive.downloadFile(id)
return send_file(ioBytes, mime, True, name)
[我使用示例here中的下载方法,进行了很小的更改
def downloadFile(self, file_id):
file = self.drive.files().get(fileId=file_id).execute()
request = self.drive.files().get_media(fileId=file_id)
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
status, done = downloader.next_chunk()
print("Downloading {} - {}%".format(file.get('name'), int(status.progress() * 100)))
fh.seek(0)
return (fh, file.get('name'), file.get('mimeType'))
它按预期工作,并将文件下载到我的计算机。
现在,我想将此Flask应用程序部署到Heroku。我的问题是HTTP超时,如here:
所述HTTP请求有一个最初的30秒窗口,Web进程必须在该窗口中返回响应数据
由于我的某些文件可能需要30秒钟以上的下载时间,因此最终成为一个大问题。
我尝试使用Response类和yield语句来保持发送空字节,直到使用以下功能下载并发送文件为止:
def sendUntilEndOfRequest(func, args=()):
def thread():
with app.app_context(), app.test_request_context():
return func(*args)
with concurrent.futures.ThreadPoolExecutor() as executor:
ret = ""
def exec():
while ret == "":
yield ""
time.sleep(1)
yield ret
future = executor.submit(thread)
def getValue():
nonlocal ret
ret = future.result()
threading.Thread(target=getValue).start()
return Response(stream_with_context(exec()))
我试图使它具有某种通用性,以便如果我有其他需要花费超过30秒才能执行的函数,则可以使用它。
现在,我的下载代码是
@app.route("/downloadFile/<id>")
def downloadFile(id):
def downloadAndSendFile():
ioBytes, name, mime = gdrive.downloadFile(id)
return send_file(ioBytes, mime, True, name)
return sendUntilEndOfRequest(downloadAndSendFile)
但是每次我尝试运行此代码时,都会出现此错误:
127.0.0.1 - - [15/Jan/2020 20:38:06] "[37mGET /downloadFile/1heeoEBZrhW0crgDSLbhLpcyMfvXqSmqi HTTP/1.1[0m" 200 -
Error on request:
Traceback (most recent call last):
File "C:\Users\fsvic\AppData\Local\Programs\Python\Python37\lib\site-packages\werkzeug\serving.py", line 303, in run_wsgi
execute(self.server.app)
File "C:\Users\fsvic\AppData\Local\Programs\Python\Python37\lib\site-packages\werkzeug\serving.py", line 294, in execute
write(data)
File "C:\Users\fsvic\AppData\Local\Programs\Python\Python37\lib\site-packages\werkzeug\serving.py", line 274, in write
assert isinstance(data, bytes), "applications must write bytes"
AssertionError: applications must write bytes
显然,文件下载正确。我测试了用send_file
命令替换render_template
来检查是否可以产生烧瓶对象,并且它工作得很好。我还测试了返回的字符串,它也可以正常工作。
最后,如何获取下载的文件?
所有MediaIoBaseDownload
所做的都是调用文件处理程序的write
方法。因此,您可以像这样实现自己的IO: