在Flask中从私有的AWS S3存储桶中提供静态文件

15
我正在开发一个Flask应用程序,运行在Heroku上,允许用户上传图片。该应用程序有一个页面,在表格中显示用户的图片。
为了开发目的,我将上传的文件保存到Heroku的ephemeral文件系统中,一切正常:图片正确加载和显示(我使用的是here中显示的最后一种方法,暗示使用send_from_directory())。现在我已经将存储迁移到S3并尝试适应代码。我使用boto3将文件上传到存储桶中:这很好用。我的疑问与下载以填充用户页面相关。
here所述,我可以将文件设置为“public-read”并使用URL(我认为这就是Flask-S3的作用),但我宁愿不要留下免费访问文件的方式。因此,我的解决方案尝试是将文件下载到Heroku的文件系统中,并再次使用send_from_directory()服务图像,如下所示:

app.py

@app.route('/download/<resource>')
def download_image(resource):
    """ resource: name of the file to download"""
    s3 = boto3.client('s3',
                      aws_access_key_id=current_app.config['S3_ACCESS_KEY'],
                      aws_secret_access_key=current_app.config['S3_SECRET_KEY'])

    s3.download_file(current_app.config['S3_BUCKET_NAME'],
                     resource,
                     os.path.join('tmp',
                                  resource))

    return send_from_directory('tmp',  # Heroku's filesystem
                               resource,
                               as_attachment=False)

然后,在模板中,我生成图像的URL如下:

...
<img src="{{ url_for('app.download_image',
                     resource=resource) }}" height="120" width="120">
...

它可以工作,但出于某些原因,我认为这不是正确的方式:其中之一是,我应该管理Heroku的文件系统以避免在动态重启之间使用完所有空间(我应该从文件系统中删除图像)。

哪种方式是最佳/首选方式,还考虑到性能?非常感谢

1个回答

23

最佳方式是简单地创建预签名URL以获得图像,并返回重定向到该URL。这将使文件在S3中保持私有,但生成一个临时的、时间限制的URL,可用于直接从S3下载文件。这将大大减少服务器上发生的工作量,以及服务器消耗的数据传输量。类似这样:

@app.route('/download/<resource>')
def download_image(resource):
    """ resource: name of the file to download"""
    s3 = boto3.client('s3',
                      aws_access_key_id=current_app.config['S3_ACCESS_KEY'],
                      aws_secret_access_key=current_app.config['S3_SECRET_KEY'])

    url = s3.generate_presigned_url('get_object', Params = {'Bucket': 'S3_BUCKET_NAME', 'Key': resource}, ExpiresIn = 100)
    return redirect(url, code=302)

如果您不喜欢那个解决方案,您可以至少考虑从S3中流式传输文件内容,而不是将其写入文件系统。


只是想指出,这种方法可能会绕过每个用户的安全控制,因为任何拥有该链接的人都可以在分配的时间窗口内访问该文件。 - jaywon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接