繁体   English   中英

S3 Python-使用预先签名的部分网址分段上传到s3

[英]S3 Python - Multipart upload to s3 with presigned part urls

我尝试使用预先签名的部分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>

即使上传仍然存在,我也可以列出它。

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

您是否尝试了预签名的POST 这是适用于它的AWS Python参考: https : //docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-presigned-post.html

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

预签名的POST示例

作为最后的选择,您始终可以尝试使用旧的REST API,尽管我认为问题不在您的代码中,也不在boto3中: https ://docs.aws.amazon.com/AmazonS3/latest/dev/UsingRESTAPImpUpload .html

是一个功能完全相同的命令实用程序,您可能想在尝试时提供它,看看它是否有效。 如果这样做的话,很容易找到您的代码与其代码之间的差异。 如果没有,我将仔细检查整个过程。 这是一个如何使用aws命令行https://aws.amazon.com/premiumsupport/knowledge-center/s3-multipart-upload-cli/?nc1=h_ls上传文件的示例

其实是否可行。 也就是说,您可以使用aws s3命令来补充上传内容,然后我们需要集中精力使用分配的url。 您可以在此处检查网址的外观:

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

这是js sdk,但那里的人谈论的是原始网址和参数,因此您应该能够发现自己的网址和有效网址之间的差异。

另一个选择是尝试使用此脚本,它使用js从网络浏览器使用指定的URL上传文件。

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

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

顺便说一句。 一旦有了用于分段上传的有效URL,就可以使用aws s3 presign url获取分配的url,这应该使您仅使用curl即可完成上传,从而完全控制上传过程。

预设网址方法

您可以在以下链接中研究适用于Python SDK(Boto3)的AWS S3预签名URL以及如何使用分段上传API。

  1. Amazon S3示例>>预设URL
  2. Amazon S3的Python代码示例>> 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安全令牌服务(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,该SDK实现了更简单的API,避免了分段上传的具体细节。

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

针对适用于Python的MinIO Client SDK尝试以下代码:

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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM