Amazon Web Services 한국 블로그

서버리스를 이용하여 Amazon S3에 직접 파일 업로드 하기

웹 및 모바일 애플리케이션에서 사용자 데이터 업로드는 가장 많이 만드는 기능입니다. 사용자가 사진, 동영상, 문서와 같은 미디어 파일을 업로드하도록 허용할 수 있습니다. 일반적으로 웹 서버 기반 환경에서 프로세스는 다음 흐름을 따릅니다.

애플리케이션 서버 업로드 프로세스

  1. 사용자가 파일을 애플리케이션 서버에 업로드합니다.
  2. 애플리케이션 서버는 처리를 위해 업로드를 임시 공간에 저장합니다.
  3. 파일을 데이터베이스, 파일 서버 또는 영구 저장을 위한 개체 저장소로 전송합니다.

과정은 간단하지만 사용량이 웹 서버의 성능에 심각한 부작용이 있을 수 있습니다.  파일 크기가 큰 미디어 업로드는 네트워크 I/O 및 서버 CPU를 많이 잡아 먹을수 있습니다. 또한, 파일들이 성공적으로 업로드되도록 전송 상태를 관리하고 재시도 및 오류를 관리해야 합니다.

이는 특정 시점에 트래픽 패턴이 급증하는 애플리케이션의 경우 매우 어렵습니다. 예를 들어, 휴일 인사말을 전문으로 보내는 웹 애플리케이션에서 대부분의 트래픽은 휴일에만 발생할 수 있습니다. 수천 명의 사용자가 동시에 미디어 업로드를 시도하는 경우, 애플리케이션 서버를 확장하고 사용 가능한 네트워크 대역폭이 충분한지도 확인해야 합니다.

AWS의 무제한 스토리지 서비스인 Amazon S3에 파일을 직접 업로드하면,  웹서버의 네트워크 트래픽과 서버 CPU 사용량을 크게 줄이고, 서버가 사용량이 많은 기간 동안 다른 요청을 처리할 수 있습니다. Amazon S3는 가용성과 내구성이 뛰어나 사용자 업로드를 위한 이상적인 영구 저장소입니다.

이 글에서는 서버 저장소가 아니라 Amazon S3로 직접 파일을 업로드하는 경우, 서버리스 방식으로 업로드를 구현하는 방법을 소개합니다. (예제로 만든 Happy Path 웹 애플리케이션GitHub 리포지토리에서 받으실 수 있습니다.)

서버리스 기반 Amazon S3 파일 업로드 개요

Amazon S3 버킷에 파일을 직접 업로드하는 경우, 먼저 S3 서비스에서 서명된 URL을 요청해야 합니다. 그런 다음 서명된 URL을 사용하여 직접 업로드할 수 있습니다.

프론트엔드 애플리케이션에서 아래 두 개의 단계로 구성됩니다.

S3에 서버리스 업로드

  1. Amazon API Gateway 를 호출하여, getSignedURL Lambda 함수엔드포인트를 호출합니다. 이것은 Signed URL을 얻을 수 있습니다.
  2. 애플리케이션에서 S3 버킷으로 파일을 직접 업로드합니다.

AWS 계정에 S3 업로더 예제를 배포하려면:

  1. S3 업로더 리포지토리로 이동하여 README.md에 나열된 전제 조건을 설치합니다.
  2. 터미널 창에서 다음을 실행합니다.
    git clone https://github.com/aws-samples/amazon-s3-presigned-urls-aws-sam
    cd amazon-s3-presigned-urls-aws-sam
    sam deploy --guided
  3. 프롬프트에서 스택 이름에 s3uploader를 입력 하고, 원하는 리전을 선택합니다. 배포가 완료되면 APIendpoint 출력을 확인합니다. API 엔드 포인트 값은 기본 URL입니다. 업로드 URL은 다음을 포함하는 API 엔드포인트입니다. /uploads추가됨. 예를 들어: https://ab123345677.execute-api.us-west-2.amazonaws.com/uploads.

CloudFormation 스택 출력

파일 업로드 기능 테스트하기

여기서는 업로드 기능을 테스트하는 두 가지 방법을 보여줍니다. 첫 번째는 Postman 을 사용하여 API를 직접 호출하고 서명된 URL이 포함된 바이너리 파일을 업로드할 수 있습니다. 두 번째는 API 통합 방법을 보여주는 기본 프론트엔드 애플리케이션을 사용하는 것입니다.

Postman을 사용하여 테스트하려면:

  1. 먼저 배포 출력에서 ​​API 엔드포인트를 복사합니다.
  2. Postman 인터페이스에서 Enter request URL 에 API 엔드포인트를 상자에 붙여넣습니다.
  3. Send를 선택합니다.우편 배달부 테스트
  4. 요청이 완료되면 Body 섹션에 JSON 응답이 표시됩니다. uploadUrl의 속성은 서명된 URL이 포함되어 있습니다. 이 속성을 클립보드에 복사합니다.
  5. + 아이콘를 선택하여 새 요청을 만듭니다.
  6. 드롭다운을 사용하여 방법을 GET에서 PUT으로 변경합니다. 요청 URL 입력 상자에 URL을 붙여넣습니다
  7.  Body 탭을 선택한 다음, Binary 라디오 버튼을 선택 합니다. Postman에서 바이너리 라디오 버튼을 선택하십시오.
  8. 파일 선택하고 업로드할 JPG 파일을 선택합니다.
    Send를 선택합니다. 파일을 업로드 한 후 200 OK 응답이 나오는지 확인합니다. Postman의 200 응답 코드
  9. S3 콘솔로 이동하여 배포에서 생성된 S3 버킷을 엽니다. 버킷에는 Postman을 통해 업로드된 JPG 파일이 표시됩니다. S3 버킷에 업로드된 객체

샘플 프론트엔드 애플리케이션으로 테스트하려면:

  1.  index.html 예제의 리포지토리에서 S3 버킷을 복사 합니다.
  2. 공개적으로 읽을 수 있도록, 개체 권한 업데이트을 합니다.
  3. 브라우저에서 index.html의 공개 URL로 이동합니다.index.html의 프론트엔드 테스트 앱
  4. 파일 선택기에서 업로드할 JPG 파일을 선택합니다.  업로드가 완료되면 확인 메시지가 표시됩니다. 테스트 앱에서 업로드
  5. S3 콘솔로 이동하여 S3 버킷을 엽니다. 버킷에는 브라우저에서 업로드한 두 번째 JPG 파일이 표시됩니다. S3 버킷에 두 번째로 업로드된 파일

Amazon S3 업로드 프로세스 이해

웹 애플리케이션에서 S3로 객체를 업로드할 때, Cross-Origin Resource Sharing(CORS)를 위해 S3를 구성 해야 합니다. CORS 규칙은 버킷의 XML 문서로 정의됩니다. AWS SAM을 사용하여 에서 리소스 정의의 일부로 CORS를 구성할 수 있습니다. 아래는 AWS SAM 템플릿의 사용 예제입니다.

   S3UploadBucket:
    Type: AWS::S3::Bucket
    Properties:
      CorsConfiguration:
        CorsRules:
        - AllowedHeaders:
            - "*"
          AllowedMethods:
            - GET
            - PUT
            - HEAD
          AllowedOrigins:
            - "*"
YAML

위애 배포 정책은 모든 헤더와 출처를 허용합니다. 프로덕션 워크로드에 대해서는 더 제한적인 정책을 사용하는 것이 좋습니다.

첫 번째 단계에서 API 엔드포인트는 Lambda 함수를 호출하여 서명된 URL을 요청합니다. 람다 함수는 다음과 같은 코드가 포함되어 있습니다.

const AWS = require('aws-sdk')
AWS.config.update({ region: process.env.AWS_REGION })
const s3 = new AWS.S3()
const URL_EXPIRATION_SECONDS = 300

// Main Lambda entry point
exports.handler = async (event) => {
  return await getUploadURL(event)
}

const getUploadURL = async function(event) {
  const randomID = parseInt(Math.random() * 10000000)
  const Key = `${randomID}.jpg`

  // Get signed URL from S3
  const s3Params = {
    Bucket: process.env.UploadBucket,
    Key,
    Expires: URL_EXPIRATION_SECONDS,
    ContentType: 'image/jpeg'
  }
  const uploadURL = await s3.getSignedUrlPromise('putObject', s3Params)
  return JSON.stringify({
    uploadURL: uploadURL,
    Key
  })
}
JavaScirpt

이 람다 함수는 임의의 숫자를 사용하여 업로드된 개체의 이름 또는 키를 결정합니다. s3Params의 객체는 허용 된 콘텐츠 형식을 정의하고, 키의 만료를 지정합니다. 이 경우 키는 300초 동안 유효합니다. 서명된 URL은 호출 애플리케이션의 키를 포함하는 JSON 객체의 일부로 반환됩니다.

서명된 URL에는 이 단일 객체를 이 버킷에 업로드할 수 있는 권한이 있는 보안 토큰이 포함되어 있습니다. 이 토큰을 성공적으로 생성하려면, getSignedUrlPromise 를 호출하는 코드가 버킷에 대해  s3:putObject 권한이 있어야 합니다. Lambda 함수는 AWS SAM 템플릿에 의해 S3WritePolicy 버킷 정책에 따릅니다.

업로드된 개체는 매개변수에 정의된 것과 동일한 파일 이름 및 콘텐츠 유형과 일치해야 합니다. 토큰이 만료되기 전에 업로드 프로세스가 시작되는 경우, 매개변수와 일치하는 개체를 여러 번 업로드할 수 있습니다. 기본 만료 시간은 15분이지만 사용 사례에 따라 더 짧은 만료 시간을 지정할 수 있습니다.

프론트엔드 애플리케이션이 API 엔드포인트 응답을 수신하면, 서명된 URL을 받습니다. 그런 다음 PUT 메서드를 사용하여 바이너리 데이터를 서명된 URL에 직접 업로드합니다.

let blobData = new Blob([new Uint8Array(array)], {type: 'image/jpeg'})
const result = await fetch(signedURL, {
  method: 'PUT',
  body: blobData
})
JavaScript

이 시점에서 호출 애플리케이션은 API 엔드포인트 또는 Lambda 함수가 아닌 S3 서비스와 직접 상호 작용합니다. 업로드가 완료되면 S3에서 200 HTML 상태 코드를 반환합니다.

이를 통해 대량 파일 업로드가 예상되는 애플리케이션의 경우, 백엔드 서버와 상관 없이 Amazon S3로 많은 양의 네트워크 트래픽을 분산할 수 있습니다.

업로드 프로세스에 인증 추가하기

현재 API 엔드포인트가 공개되어 있으며, 서명된 URL을 받으면 누구나 JPG 파일을 업로드할 수 있습니다. 사실 개발자는 인증을 사용하여 API에 액세스할 수 있는 사람과 S3 버킷에 파일을 업로드할 수 있는 사람을 제어할 수 있어야 합니다.

권한 부여자를 사용하여 이 API에 대한 액세스를 제한할 수 있습니다. 이 샘플은 HTTP API를 사용하여, JWT 권한 부여자를 이용할 수 있습니다. Amazon Cognito 또는 Auth0를 통해 API에 대한 액세스를 제어할 수 있습니다.

Happy Path 앱에서 로그인한 사용자가 파일을 업로드하도록 ID 공급자로 Auth0를 사용합니다. AWS SAM 샘플 템플릿에 templateWithAuth.yaml 파일에 API에 권한 부여를 추가하는 방법을 보여줍니다.

  MyApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      Auth:
        Authorizers:
          MyAuthorizer:
            JwtConfiguration:
              issuer: !Ref Auth0issuer
              audience:
                - https://auth0-jwt-authorizer
            IdentitySource: "$request.header.Authorization"
        DefaultAuthorizer: MyAuthorizer
YAML

발급자 및 대상 속성은 모두 Auth0 구성에서 제공됩니다. 이를 기본 권한 부여자로 지정하면, API를 사용하는 모든 경로에 자동으로 사용됩니다. HTTP API로 Auth0 및 권한 부여자를 구성하는 방법에 대해 자세히 알아보려면 Ask Around Me 시리즈 를 살펴보세요.

인증이 추가된 후 호출 웹 애플리케이션은 요청 헤더에 JWT 토큰을 제공합니다.

const response = await axios.get(API_ENDPOINT_URL, {
  headers: {
    Authorization: `Bearer ${token}`
        }
})
JavaScript

getUploadURL Lambda 함수를 호출 하기 전에 API Gateway는 이 토큰을 평가합니다. 이렇게 하면 인증된 사용자만 S3 버킷에 객체를 업로드할 수 있습니다.

권한 수정을 통해 파일 공개하기

현재 업로드된 파일은 공개적으로 액세스할 수 없습니다. 업로드된 객체를 공개적으로 읽을 수 있도록 하려면 액세스 제어 목록설정을 해야 합니다. 인터넷의 모든 사용자가 객체를 읽을 수 있도록 하는 공개 읽기 옵션을 포함하여 S3에서 사용할 수 있는 사전 구성된 ACL이 있습니다.

const s3Params = {
  Bucket: process.env.UploadBucket,
  Key,
  Expires: URL_EXPIRATION_SECONDS,
  ContentType: 'image/jpeg',
  ACL: 'public-read'
}
JavaScript

Lambda 함수에는 요청에 서명할 수 있는 적절한 버킷 권한이 있어야 하므로 함수에 PutObjectAcl 권한도 있는지 확인해야 합니다. AWS SAM에서 다음 정책을 사용하여 Lambda 함수에 대한 권한을 추가할 수 있습니다.

        - Statement:
          - Effect: Allow
            Resource: !Sub 'arn:aws:s3:::${S3UploadBucket}/'
            Action:
              - s3:putObjectAcl
YAML

마무리

웹 및 모바일 애플리케이션을 통해 사용자는 이미지 및 비디오와 같은 대용량 미디어 파일을 포함한 데이터를 업로드할 수 있습니다. 기존의 서버 기반 응용 프로그램에서 서버에 과중한 로드를 생성하고 상당한 양의 네트워크 대역폭을 사용할 수도 있습니다.

서버리스 기법을 통해 사용자가 Amazon S3에 파일을 업로드할 수 있도록 함으로써, 네트워크 부하를 분산시켜 주 애플리케이션을 훨씬 더 확장 가능하게 만들고 급증하는 트래픽을 처리할 수 있습니다.

이 글은 서버리스 기반 파일 업로드 샘플 애플리케이션을 통해 S3에서 서명된 URL을 만들어, Postman과 웹 애플리케이션 모두에서 URL을 테스트하는 방법, 마지막으로 인증을 추가하고 업로드된 객체를 공개적으로 액세스할 수 있도록 하는 방법을 설명했습니다.

더 자세히 알아보려면, 웹 애플리케이션에서 S3로 직접 업로드하는 방법을 보여주는 동영상을 참고하세요. (예제로 만든 Happy Path 웹 애플리케이션GitHub 리포지토리에서 받으실 수 있습니다.) 더 많은 서버리스 학습 리소스를 보려면 Serverless Land 웹 사이트도 살펴보세요.

– James Beswick, AWS Serverless Developer Advocate

이 글은 AWS Compute Blog의 Uploading to Amazon S3 directly from a web or mobile application 한국어 번역입니다.