【问题标题】:Downloading a file from an s3 Bucket to the USERS computer将文件从 s3 Bucket 下载到 USERS 计算机
【发布时间】:2017-08-30 03:57:22
【问题描述】:

目标

将文件从 s3 Bucket 下载到用户计算机。

上下文

我正在为 React 应用程序开发 Python/Flask API。当用户点击前端的下载按钮时,我想将相应的文件下载到他们的机器上。

我尝试过的

import boto3 s3 = boto3.resource('s3') s3.Bucket('mybucket').download_file('hello.txt', '/tmp/hello.txt')

我目前正在使用一些代码来查找下载文件夹的路径,然后将该路径插入到 download_file() 作为第二个参数,以及他们尝试下载的存储桶上的文件。

这在本地工作,并且测试运行良好,但是一旦部署就遇到了问题。代码会找到服务器的下载路径,并在那里下载文件。

问题

解决此问题的最佳方法是什么?我已经研究过并且找不到能够将文件从 s3 存储桶下载到用户下载文件夹的好的解决方案。非常感谢任何帮助/建议。

【问题讨论】:

  • 这完全取决于您的用户如何连接到服务器。如果是通过浏览器,那么您应该创建一个新端点来下载文件,并在您的应用程序中提供指向端点的链接。如果您正在编写本机应用程序,则需要设置某种 RPC 以从服务器获取文件。

标签: python api amazon-s3 operating-system web-development-server


【解决方案1】:

您不需要将文件保存到服务器。您可以将文件下载到内存中,然后构建一个包含该文件的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)

这对于小文件来说没问题,用户不会有任何有意义的等待时间。但是对于较大的文件,这会很好地影响用户体验。该文件需要完全下载到服务器,然后下载给用户。因此,要解决此问题,请使用 get_object 方法的 Range 关键字参数:

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)

这将以 1MB 块的形式下载文件,并在下载后将其发送给用户。这两个都已使用 40MB .txt 文件进行了测试。

【讨论】:

  • 非常感谢您的详细回答!这非常有帮助,我能够使用此代码解决我的问题,只需稍作修改:)
  • 客户端取消下载会怎样?
  • 我自己都没有尝试过,但是看看这个answer
  • @AllieFitter get_object_range 函数中的basestring 是什么?
  • 一个 Python 2 的遗物,所以它应该使用 str 代替。我已经三年没有运行过这段代码了,所以我不确定其中还有什么与 Python 3 不兼容。
【解决方案2】:

解决此问题的更好方法是 create presigned url。这会为您提供一个在一定时间内有效的临时 URL。它还会删除您的烧瓶服务器作为 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) `

【讨论】:

    猜你喜欢
    • 2019-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 2013-12-06
    • 1970-01-01
    相关资源
    最近更新 更多