Отображение файла (изображения) с S3 через Flask & BytesIO

Я пытаюсь извлечь файл из AWS S3, используя Boto3, непосредственно в объект BytesIO. В конечном итоге это будет использоваться для управления загруженными данными, но сейчас я просто пытаюсь передать этот файл непосредственно пользователю через Flask. Насколько я понимаю, все, что ниже, должно работать, но не работает. Браузер просто ничего не отображает (и показывает только загруженные несколько байтов данных).

(В этом примере мой образец файла имеет формат png)

from flask import Flask, send_from_directory, abort, Response, send_file, make_response
import boto3, botocore
import os
import io

AWS_ACCESS_KEY = os.environ['AWS_ACCESS_KEY'].rstrip()
AWS_SECRET_KEY = os.environ['AWS_SECRET_KEY'].rstrip()
S3_BUCKET = "static1"
app = Flask(__name__, static_url_path='/tmp')

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    s3 = boto3.client('s3', aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_KEY,)
    file = io.BytesIO()
    metadata = s3.head_object(Bucket=S3_BUCKET, Key=path)
    conf = boto3.s3.transfer.TransferConfig(use_threads=False)
    s3.download_fileobj(S3_BUCKET, path, file)
    return send_file(file, mimetype=metadata['ContentType'])

if __name__ == '__main__':
     app.run(debug=True,port=3000,host='0.0.0.0')

Если я изменю эту базовую процедуру для записи объекта BytesIO на диск, а затем прочитаю его обратно в новый объект BytesIO - все будет работать нормально. Как показано ниже:

def catch_all(path):
    s3 = boto3.client('s3', aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_KEY,)
    file = io.BytesIO()
    metadata = s3.head_object(Bucket=S3_BUCKET, Key=path)
    conf = boto3.s3.transfer.TransferConfig(use_threads=False)
    s3.download_fileobj(S3_BUCKET, path, file)
    print(file.getvalue())
    fh = open("/tmp/test1.png","wb")
    fh.write(file.getvalue())
    fh.close()
    fh = open("/tmp/test1.png","rb")
    f2 = io.BytesIO(fh.read())
    fh.close
    print(f2.getvalue())
    return send_file(f2, mimetype=metadata['ContentType'])

Ходя по кругу с этим в течение нескольких дней, ясно, что я что-то упускаю, и я не уверен, что. Скрипт запускается внутри док-контейнера Python 3.8 с последними копиями boto3/flask/etc.


person Jon    schedule 25.11.2019    source источник


Ответы (1)


Перемотка вашего объекта BytesIO должна помочь, с file.seek(0) непосредственно перед send_file(...).

Для справки, я не уверен, что ваши вызовы boto3/botocore являются «лучшими практиками», чтобы попробовать ваш вариант использования, с которым я столкнулся:

from boto3.session import Session

session = Session(
    aws_access_key_id=KEY_ID, aws_secret_access_key=ACCESS_KEY, region_name=REGION_NAME
)
s3 = session.resource("s3")


@base_bp.route("/test-stuff")
def test_stuff():
    a_file = io.BytesIO()
    s3_object = s3.Object(BUCKET, PATH)
    s3_object.download_fileobj(a_file)
    a_file.seek(0)
    return send_file(a_file, mimetype=s3_object.content_type)

Он работает при чтении файла с диска, потому что вы создаете экземпляр BytesIO с полным содержимым файла, поэтому он правильно выполнен и все еще находится в «позиции 0».

person b4stien    schedule 25.11.2019
comment
Ага. что object.seek(0) было именно так. Спасибо и за объяснение! - person Jon; 26.11.2019