Te recomendamos que revises esta resolución en un entorno controlado antes de pasarlo a producción, saludos.
Solución:
No debería necesitar guardar el archivo en el servidor. Simplemente puede descargar el archivo en la memoria y luego crear un Response
objeto que contiene el archivo.
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)
Esto está bien para archivos pequeños, no habrá ningún tiempo de espera significativo para el usuario. Sin embargo, con archivos más grandes, esto afectará la UX. El archivo deberá descargarse completamente en el servidor y luego descargarse al usuario. Entonces, para solucionar este problema, use el Range
argumento de palabra clave del get_object
método:
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, str) 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)
Esto descargará el archivo en fragmentos de 1 MB y los enviará al usuario a medida que se descargan. Ambos han sido probados con un 40MB .txt
expediente.
Una mejor manera de resolver este problema es crear una URL prefirmada. Esto le da una URL temporal que es válida hasta un cierto período de tiempo. También elimina su servidor de matraz como un proxy entre el depósito de AWS s3, lo que reduce el tiempo de descarga para el usuario.
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) `
Comentarios y puntuaciones del artículo
Nos encantaría que puedieras mostrar este artículo si te fue de ayuda.