Descargue el archivo de s3 Bucket a la computadora de los usuarios.
Estoy trabajando en una API Python/Flask para una aplicación React. Cuando el usuario hace clic en el botón Descargar en el front-end, quiero descargar el archivo apropiado en su máquina.
import boto3 s3 = boto3.resource('s3') s3.Bucket('mybucket').download_file('hello.txt', '/tmp/hello.txt')
Actualmente estoy usando un código que encuentra la ruta de la carpeta de descargas y luego conecto esa ruta en download_file() como el segundo parámetro, junto con el archivo en el depósito que están tratando de descargar.
Esto funcionó localmente y las pruebas funcionaron bien, pero me encontré con un problema una vez que se implementó. El código encontrará la ruta de descarga del SERVIDOR y descargará el archivo allí.
¿Cuál es la mejor manera de abordar esto? He investigado y no puedo encontrar una buena solución para poder descargar un archivo del depósito s3 a la carpeta de descargas de los usuarios. Cualquier ayuda/consejo es muy apreciada.
No debería necesitar guardar el archivo en el servidor. Simplemente puede descargar el archivo en la memoria y luego crear un objeto de Response que contenga 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 argumento de la palabra clave Range del método 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, 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 archivo .txt de 40 MB.
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) `