【问题标题】:S3 Python - Multipart upload to s3 with presigned part urlsS3 Python - 使用预签名的部分网址分段上传到 s3
【发布时间】:2019-09-13 19:32:16
【问题描述】:

我没有成功尝试使用预签名部分 URL 进行分段上传。

这是我遵循的过程(1-3在服务器端,4在客户端):

  1. 实例化 boto 客户端。
import boto3
from botocore.client import Config

s3 = boto3.client(
    "s3",
    region_name=aws.default_region,
    aws_access_key_id=aws.access_key_id,
    aws_secret_access_key=aws.secret_access_key,
    config=Config(signature_version="s3v4")
)
  1. 启动分段上传。
upload = s3.create_multipart_upload(
    Bucket=AWS_S3_BUCKET,
    Key=key,
    Expires=datetime.now() + timedelta(days=2),
)
upload_id = upload["UploadId"]
  1. 为部分上传创建一个预签名 URL。

part = generate_part_object_from_client_submited_data(...)

part.presigned_url = s3.generate_presigned_url(
    ClientMethod="upload_part",
    Params={
        "Bucket": AWS_S3_BUCKET,
        "Key": upload_key,
        "UploadId": upload_id,
        "PartNumber": part.no,
        "ContentLength": part.size,
        "ContentMD5": part.md5,
    },
    ExpiresIn=3600,  # 1h
    HttpMethod="PUT",
)

将预签名的 URL 返回给客户端。

  1. 在客户端尝试使用requests 上传该部分。
part = receive_part_object_from_server(...)

with io.open(filename, "rb") as f:
    f.seek(part.offset)
    buffer = io.BytesIO(f.read(part.size))

r = requests.put(
    part.presigned_url,
    data=buffer,
    headers={
        "Content-Length": str(part.size),
        "Content-MD5": part.md5,
        "Host": "AWS_S3_BUCKET.s3.amazonaws.com",
    },
)

当我尝试上传时,我得到:

urllib3.exceptions.ProtocolError:
('Connection aborted.', BrokenPipeError(32, 'Broken pipe'))

或者:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>NoSuchUpload</Code>
  <Message>
    The specified upload does not exist. The upload ID may be invalid,
    or the upload may have been aborted or completed.
  </Message>
  <UploadId>CORRECT_UPLOAD_ID</UploadI>
  <RequestId>...</RequestId>
  <HostId>...</HostId>
</Error>

即使上传仍然存在并且我可以列出它。

谁能告诉我我做错了什么?

【问题讨论】:

  • 你的代码对我来说是孤立的,有一个小部分类。您如何处理完整的分段上传请求?您确定在客户上传之前它没有被解雇吗?您确定您发送给客户的 URL 没有以某种方式进行转换吗?您已经非常接近拥有一个简单的测试台了,我会将其变成一个简单的端到端测试台,仅用于分段上传以验证代码,尽管我怀疑问题出在未显示的代码中。跨度>
  • 我也在尝试使用预签名的 URL 执行分段上传。你能建议你是如何克服这个问题的吗?

标签: python amazon-web-services amazon-s3 boto3


【解决方案1】:

Here 是一个执行完全相同的命令的实用程序,您可能想尝试一下,看看它是否有效。如果是这样,很容易找到您的代码和他们的代码之间的区别。如果不是,我会仔细检查整个过程。这是一个如何使用aws命令行https://aws.amazon.com/premiumsupport/knowledge-center/s3-multipart-upload-cli/?nc1=h_ls上传文件的示例

实际上,如果它确实有效。即您可以使用 aws s3 命​​令复制上传,然后我们需要关注 persigned url 的使用。您可以在此处查看 url 的外观:

https://github.com/aws/aws-sdk-js/issues/468 https://github.com/aws/aws-sdk-js/issues/1603

这是 js sdk,但那里的人谈论原始 url 和参数,所以你应该能够发现你的 url 和有效的 url 之间的区别。

另一个选择是试试这个脚本,它使用 js 使用来自网络浏览器的 persigned url 上传文件。

https://github.com/prestonlimlianjie/aws-s3-multipart-presigned-upload

如果可行,您可以检查通信并观察用于上传每个部分的确切 URL,您可以将其与系统生成的 url 进行比较。

顺便说一句。一旦你有一个用于分段上传的工作 url,你可以使用 aws s3 presign url 来获取 persigned url,这应该让你只使用 curl 完成上传,以完全控制上传过程。

【讨论】:

  • 嗨皮奥特。我不是在下载,而是在进行分段上传。当我测试代码时,一切都是在同一台机器上完成的,所以这不是IP的变化。
  • 对,谢谢。我明白了一点,并更新了答案。\
【解决方案2】:

您是否尝试过pre-signed POST?这是它的 AWS Python 参考:https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-presigned-post.html

这可能会从客户端的角度解决代理限制(如果有的话):

作为最后的手段,你总是可以尝试好的旧 REST API,虽然我不认为问题出在你的代码和 boto3 中:https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingRESTAPImpUpload.html

【讨论】:

  • 您好法比奥,谢谢您的回答。我没有代理上传,所以我在命令行客户端和 AWS 之间不使用 Django 或其他任何东西。我还找到了那个博客页面并按照它做了一切,但我无法让它工作。此外,部分的上传失败,所以我什至没有达到完成上传的代码。
  • 编辑重试:)
  • 这个不是分段上传:(
  • 再试一次! :D
【解决方案3】:

预签名 URL 方法

您可以在以下链接中学习适用于 Python 开发工具包 (Boto3) 的 AWS S3 预签名 URL 以及如何使用分段上传 API:

  1. Amazon S3 Examples >> Presigned URLs
  2. Python Code Samples for Amazon S3 >> generate_presigned_url.py
  3. Boto3 - S3 - create_multipart_upload
  4. Boto3 - S3 - complete_multipart_upload

转会经理方法

Boto3 提供了使用 S3 管理各种类型传输的接口,以自动管理分段和非分段上传。为确保仅在绝对必要时进行分段上传,您可以使用 multipart_threshold 配置参数。

为 Transfer Manager 方法尝试以下代码:

import boto3
from boto3.s3.transfer import TransferConfig
import botocore
from botocore.client import Config
from retrying import retry
import sysdef upload(source, dest, bucket_name):
    try:
        conn = boto3.client(service_name="s3", 
                            aws_access_key_id=[key],    
                            aws_secret_access_key=[key],                   
                            endpoint_url=[endpoint],
                            config=Config(signature_version='s3')
        config = TransferConfig(multipart_threshold=1024*20,  
                        max_concurrency=3, 
                        multipart_chunksize=1024*20, 
                        use_threads=True)
        conn.upload_file(Filename=source, Bucket=bucket_name,     
                         Key=dest, Config=config)
    except Exception as e:
        raise Exception(str(e))def download(src, dest, bucket_name):
    try:
        conn = boto3.client(service_name="s3", 
                            aws_access_key_id=[key],    
                            aws_secret_access_key=[key],                   
                            endpoint_url=[endpoint],
                            config=Config(signature_version='s3')
        config = TransferConfig(multipart_threshold=1024*20,  
                        max_concurrency=3, 
                        multipart_chunksize=1024*20, 
                        use_threads=True)
        conn.download_file(bucket=bucket_name, key=src, 
                          filename=dest, Config=config)                
    except AWSConnectionError as e:
        raise AWSConnectionError("Unable to connect to AWS")
    except Exception as e:
        raise Exception(str(e))if __name__ == '__main__': 
upload(source, dest, bucket_name)
download(src, dest, bucket_name)

AWS STS 方法

您也可以按照 AWS Security Token Service (STS) 方法生成一组临时凭证来完成您的任务。

为 AWS STS 方法尝试以下代码:

import json
from uuid import uuid4

import boto3


def get_upload_credentials_for(bucket, key, username):
    arn = 'arn:aws:s3:::%s/%s' % (bucket, key)
    policy = {"Version": "2012-10-17",
              "Statement": [{
                  "Sid": "Stmt1",
                  "Effect": "Allow",
                  "Action": ["s3:PutObject"],
                  "Resource": [arn],
              }]}
    client = boto3.client('sts')
    response = client.get_federation_token(
        Name=username, Policy=json.dumps(policy))
    return response['Credentials']


def client_from_credentials(service, credentials):
    return boto3.client(
        service,
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken'],
    )


def example():
    bucket = 'mybucket'
    filename = '/path/to/file'

    key = uuid4().hex
    print(key)

    prefix = 'tmp_upload_'
    username = prefix + key[:32 - len(prefix)]
    print(username)
    assert len(username) <= 32  # required by the AWS API

    credentials = get_upload_credentials_for(bucket, key, username)
    client = client_from_credentials('s3', credentials)
    client.upload_file(filename, bucket, key)
    client.upload_file(filename, bucket, key + 'bob')  # fails


example()

MinIO Client SDK for Python 方法

您可以使用 MinIO Client SDK for Python,它实现了更简单的 API,以避免分段上传的繁琐细节。

例如,您可以使用简单的fput_object(bucket_name, object_name, file_path, content_type) API 来满足需求。

尝试使用 MinIO Client SDK for Python 方法的以下代码:

from minio import Minio
from minio.error import ResponseError

s3client = Minio('s3.amazonaws.com',
                    access_key='YOUR-ACCESSKEYID',
                    secret_key='YOUR-SECRETACCESSKEY')

# Put an object 'my-objectname' with contents from 'my-filepath'

try:    
    s3client.fput_object('my-bucketname', 'my-objectname', 'my-filepath')
except ResponseError as err:
    print(err)

【讨论】:

    猜你喜欢
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-26
    • 2015-06-25
    • 1970-01-01
    相关资源
    最近更新 更多