如何让用户直接把图片上传到对象存储 S3

Posted wzlinux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何让用户直接把图片上传到对象存储 S3相关的知识,希望对你有一定的参考价值。

问题背景

在 Web 和移动应用程序中,为用户提供上传数据的功能是很常见的。您的应用程序可能允许用户上传 PDF 和文件、照片或视频等媒体。每个现代 Web 服务器技术都有允许此功能的机制。通常,在基于服务器的环境中,该过程遵循此流程:

  1. The user uploads the file to the application server.
  2. The application server saves the upload to a temporary space for processing.
  3. The application transfers the file to a database, file server, or object store for persistent storage.

虽然该过程很简单,但它可能会对繁忙应用程序中的 Web 服务器的性能产生显着的副作用。媒体上传通常很大,因此传输这些媒体可能会占用大量的网络 I/O 和服务器 CPU 时间。您还必须管理传输状态以确保成功上传整个对象,并管理重试和错误。

这对于具有尖峰流量模式的应用程序具有挑战性。例如,在专门发送节日问候的 Web 应用程序中,它可能仅在节假日前后遇到大部分流量。如果成千上万的用户尝试在同一时间上传媒体,这需要您横向扩展应用服务器并确保有足够的网络带宽可用。

通过将这些文件直接上传到 Amazon S3,您可以避免通过您的应用程序服务器代理这些请求。这可以显着减少网络流量和服务器 CPU 使用率,并使您的应用程序服务器能够在繁忙时段处理其他请求。 S3 还具有高可用性和持久性,使其成为用户上传的理想持久存储。

无服务上传到 S3

架构

当您直接上传到 S3 存储桶时,您必须首先从 Amazon S3 服务请求签名 URL。然后,您可以使用签名 URL 直接上传。这是您的应用程序前端的两步过程:

  1. Call an Amazon API Gateway endpoint, which invokes the s3-presigned-url Lambda function. This gets a signed URL from the S3 bucket.
  2. Directly upload the file from the application to the S3 bucket.

部署

我们使用 AWS SAM 来部署。

git clone https://github.com/wangzan18/s3-presigned-urls.git
cd s3-presigned-urls
sam deploy --guided

当部署完成之后,会输出一个 API 的终端节点:

测试

测试为两个步骤,第一步是通过请求获取一个预签名的 URL,第二步是向这个 URL PUT 一个图片。

第一步:

{"url": "https://s3-presigned-url-s3uploadbucket-41pnizqboni7.s3.amazonaws.com/3504834451.jpg?AWSAccessKeyId=ASIA5NAGHF6N7XQ72YIF&Signature=F5N0pymJB1J7ZpzgRRy6FLSX2BY%3D&content-type=image%2Fjpeg&x-amz-security-token=IQoJb3JpZ2luX2VjEHsaCWV1LXdlc3QtMSJIMEYCIQDiz5UvT4x5bMGbR4tyH4Af9jTiPeGaRRI%2F%2B24MrxasmwIhAPOFqy3A7hq5inu6Jbppc37xhnU3ygZn1FFTJeUhNpzYKsICCIT%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQAhoMOTIxMjgzNTM4ODQzIgzb0OFdLrNq1xQ5rVMqlgJ5xuc80KvUyYF6FtRahmpkPVZkh8G4BG2SqFIDCXYRQJqOVpIKXplt3ct5DjZ%2FRAjosV85lnB4SxDBC6PvFaRFsRfmk6wmmJJBqNAr6hXOr%2FMnlpwe2dwC1ks7OP7xHmZqGnDjlNebLhs1x9osb61BYfmrwjJg18LqFAra1O%2FvoPJcxbWniHwYxuGAYacs4%2FcXMKKQadyOzrdv6w4g54PprSZhFL5QHVCWx2FpNIqnb7YOUDKF%2BydQMnFywuA6lnShOeZLAWjTExdYpZCIA0i0vcEBPDN2r86LYv%2BJqpTbb%2BDlnGlWAz6HxLwbWrvQ8vNOVt0coTeOqjmbdhdwFoonhv9oArH%2FnXk5yKPLYuKGolFAmuFdPzC6t4iIBjqZAapNMWAxIdJJdBojvErX2Ybnwr%2F2b%2B0aq%2BIQ0uQb3IfZeopurj00xI1L3VX0qFbP%2FMn2yhhTy6rjAVdBgBhXgBiw86dUdnke9e4wvw%2Bwuq3iREFIoKKRyYrZ4yjUoOKqaOHdFESfR6mASv7DER0HzYhemUf39NroIDGKPYqvJMfnKoXB4wWSuKdt2DXD8%2FVLhNoB%2FjIr4QOOYw%3D%3D&Expires=1627531724", "key": "3504834451.jpg"}

拿到这个 URL,我们进行第二步,可以直接点击 URL:

这里请求需要改为 PUT,在 Body 里面选择 binary 来上传图片,然后点击提交。

可以看到请求成功,我们登录到 S3 看下图片上传如何:

这里需要注意的是,因为我们的预签名 URL 的 Key 名称已经确定了,不管我们上传的图片是什么名称,最终结果都会显示 Key 的名称。

前端测试

在我的代码仓库里面,还有一个前端的页面,可以传到 S3,然后开放公网访问,提交图片。

仅供简单测试,最重要的是后面移动应用如何对接。

模板介绍

template.yaml

这个文件是 SAM 最主要的配置文件,通过这一段参数,创建一个 HTTP API Gateway,允许一些请求参数。

  # HTTP API
  MyApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      # CORS configuration - this is open for development only and should be restricted in prod.
      # See https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-httpapi-httpapicorsconfiguration.html   
      CorsConfiguration:
        AllowMethods:
          - GET
          - POST
          - DELETE
          - OPTIONS
        AllowHeaders:
          - "*"   
        AllowOrigins: 
          - "*"     

下面这个资源比较重要一步,需要确保 Lambda 对 S3 有写的权限,并且事件驱动为 HTTP API Gateway。

  S3PreSignedUrlFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: s3-presigned-url/
      Handler: app.lambda_handler
      Policies:
        - S3WritePolicy:
            BucketName: !Ref S3UploadBucket
        ## This permission allows the Lambda function to request signed URLs
        ## for objects that will be publicly readable. Uncomment if you want this ACL.
        # - Statement:
        #   - Effect: Allow
        #     Resource: !Sub \'arn:aws:s3:::${S3UploadBucket}/\'
        #     Action:
        #       - s3:putObjectAcl
      Environment:
        Variables:
          UploadBucket: !Ref S3UploadBucket
      Runtime: python3.7
      Events:
        S3PreSignedApi:
          Type: HttpApi # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /upload
            Method: get
            ApiId: !Ref MyApi 

app.py

整个申请预签名 URL 的函数比较简单,繁琐的功能 SDK 都已经封装好了,函数会自动生成一个随机的 Key,然后返回给请求者一个 URL,通过这个 URL 可以使用 PUT 请求上传参数

import boto3
import json
import os
import random

def lambda_handler(event, context):
    bucket = os.environ.get(\'UploadBucket\')
    key = random.getrandbits(32)
    url = boto3.client(\'s3\').generate_presigned_url(
        ClientMethod=\'put_object\', 
        Params={\'Bucket\': bucket, 
                \'Key\': str(key) + \'.jpg\',
                \'ContentType\': \'image/jpeg\'},
        ExpiresIn=3600)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "url": url,
            "key": str(key) + \'.jpg\'
        }),
    }

参考文档:

https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/PresignedUrlUploadObject.html

欢迎大家扫码关注,获取更多信息

以上是关于如何让用户直接把图片上传到对象存储 S3的主要内容,如果未能解决你的问题,请参考以下文章

将存储在内存中的文件上传到s3

使用boto3批量上传图片到S3

将对象上传到 S3 存储桶时如何触发 AWS Cloudformation 堆栈的更新?

上传图片 - Nodejs Paperclip 和 S3

代号一上传图片到S3存储桶权限

从 iOS 应用上传到 Amazon S3