将文件从s3 Bucket下载到用户计算机。
我正在为React应用程序开发Python / Flask API。当用户单击前端的“下载”按钮时,我想将相应的文件下载到他们的计算机上。
import boto3
s3 = boto3.resource('s3')
s3.Bucket('mybucket').download_file('hello.txt', '/tmp/hello.txt')
我目前正在使用一些代码来查找downloads文件夹的路径,然后将该路径作为第二个参数插入download_file(),以及他们尝试下载的存储桶上的文件。
这在本地工作,测试运行正常,但是一旦部署就遇到了问题。代码将找到SERVER的下载路径,并在那里下载文件。
最好的方法是什么?我已经研究过,无法找到一个很好的解决方案,可以将文件从s3存储桶下载到用户下载文件夹。非常感谢任何帮助/建议。
答案 0 :(得分:5)
您不需要将文件保存到服务器。您只需将文件下载到内存中,然后构建一个包含该文件的Response
对象。
from flask import Flask, Response
from boto3 import client
app = Flask(__name__)
def get_client():
return client(
's3',
'us-east-1',
aws_access_key_id='id',
aws_secret_access_key='key'
)
@app.route('/blah', methods=['GET'])
def index():
s3 = get_client()
file = s3.get_object(Bucket='blah-test1', Key='blah.txt')
return Response(
file['Body'].read(),
mimetype='text/plain',
headers={"Content-Disposition": "attachment;filename=test.txt"}
)
app.run(debug=True, port=8800)
这对小文件来说没问题,用户没有任何有意义的等待时间。但是对于较大的文件,这很好地影响了UX。该文件需要完全下载到服务器,然后下载给用户。因此,要解决此问题,请使用Range
方法的get_object
关键字参数:
from flask import Flask, Response
from boto3 import client
app = Flask(__name__)
def get_client():
return client(
's3',
'us-east-1',
aws_access_key_id='id',
aws_secret_access_key='key'
)
def get_total_bytes(s3):
result = s3.list_objects(Bucket='blah-test1')
for item in result['Contents']:
if item['Key'] == 'blah.txt':
return item['Size']
def get_object(s3, total_bytes):
if total_bytes > 1000000:
return get_object_range(s3, total_bytes)
return s3.get_object(Bucket='blah-test1', Key='blah.txt')['Body'].read()
def get_object_range(s3, total_bytes):
offset = 0
while total_bytes > 0:
end = offset + 999999 if total_bytes > 1000000 else ""
total_bytes -= 1000000
byte_range = 'bytes={offset}-{end}'.format(offset=offset, end=end)
offset = end + 1 if not isinstance(end, basestring) else None
yield s3.get_object(Bucket='blah-test1', Key='blah.txt', Range=byte_range)['Body'].read()
@app.route('/blah', methods=['GET'])
def index():
s3 = get_client()
total_bytes = get_total_bytes(s3)
return Response(
get_object(s3, total_bytes),
mimetype='text/plain',
headers={"Content-Disposition": "attachment;filename=test.txt"}
)
app.run(debug=True, port=8800)
这将以1MB的块下载文件,并在下载时将其发送给用户。这两个都已使用40MB .txt
文件进行了测试。
答案 1 :(得分:3)
解决此问题的更好方法是 create presigned url。这为您提供了一个在一定时间内有效的临时URL。它还会将您的flask服务器删除为AWS s3存储桶之间的代理,从而减少了用户的下载时间。
def get_attachment_url():
bucket = 'BUCKET_NAME'
key = 'FILE_KEY'
client: boto3.s3 = boto3.client(
's3',
aws_access_key_id=YOUR_AWS_ACCESS_KEY,
aws_secret_access_key=YOUR_AWS_SECRET_KEY
)
return client.generate_presigned_url('get_object',
Params={'Bucket': bucket, 'Key': key},
ExpiresIn=60) `