[Spring] AWS S3 Multipart Upload - JavaScript SDK (feat. AWS Cognito)

Sangwoo Park·2022년 3월 29일
0

Multipart Upload

목록 보기
2/2

지난번에 REST API를 이용하여 S3 Bucket에 Multipart Upload를 하는법을 알아보았다.
이번에는 JavaScript SDK를 이용하여 대용량 파일을 청크로 나누어 업로드하는 법을 알아보자.

REST API를 사용하면

User -> Server -> AWS S3

이렇게 서버를 거치게 된다.
하지만 JavaScript SDK를 이용하면

User -> AWS S3

이렇게 프론트에서 CDN으로 바로 가게 되어 속도도 빠를 뿐 아니라, 파일이 차지할 서버의 메모리 공간도 확보할 수 있다.

우려되는 점은 AWS SDK를 사용하려면 Credential을 증명하여야 하는데,
프론트 엔드의 HTML, JS 코드는 누구나 볼 수 있기에 보안을 위해서 Access Key와 Private Key 없이 자격증명을 할 수 있는 방법을 찾아야 했다.

AWS Cognito를 사용하면 Key를 사용하지 않고도 유저가 S3 Bucket에 접근하여 파일을 업로드 할 수 있다고 해서 적용해 보았다.

📌 AWS Cognito를 이용하여 자격증명하는 과정

  • 준비사항 : AWS S3 버킷

1. 버킷의 CORS 구성을 편집하여 특정 URL에서만 접근할 수 있도록 설정

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "HEAD",
            "GET",
            "PUT",
            "POST"
        ],
        "AllowedOrigins": [
            "http://localhost:8080"
        ],
        "ExposeHeaders": [
            "ETag",
            "x-amz-meta-custom-header"
        ]
    }
]

2. Coginito에 자격증명 풀 생성

AWS Coginito 페이지에서 자격증명 풀 관리 -> 새 자격 증명 풀 만들기

사용할 풀 이름 입력 및 체크박스 활성화 -> 풀 생성

3. IAM 정책 설정

풀이 생성된 후에 IAM 역할을 생성하는 페이지로 바로 이동하게 된다

여기서 아래부분 unauthenticated identities 부분의 정책문서를 펼쳐서 편집을 해줘야 한다. 편집을 누르면 Document를 읽어야 편집이 가능하다고 하는데, Document link를 클릭해주면 편집모드로 변경된다. 정책을 변경해주자.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME/*"
            ]
        }
    ]
}

정책 입력 후 허용을 입력해주면 IAM에 정책이 자동으로 생성 된다. 나중에 IAM으로 가서 정책을 변경 해 줄 수도 있다.

이제 Cognito 자격증명 풀 대시보드로 가서 샘플코드 탭에서 JavaScript의 샘플코드를 붙여넣으면 자격증명이 된다. 플랫폼을 JavaScript로 변경하면 JS 샘플코드가 나온다.

4. 코드 작성

코드 예제 (AWS Web Javascript SDK src는 필요한 버전으로 import 해주자)

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1102.0.min.js"></script>

let s3;
const AWS_REGION = 'ap-northeast-2';
const IDENTITY_POOL_ID = 'POOL_ID'; // aws cognito 자격증명 풀 ID
AWS.config.region = AWS_REGION;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: IDENTITY_POOL_ID,
});
s3 = new AWS.S3({
  Bucket: 'BUCKET_NAME'
});

이렇게 하고 나면 JavaScript에서 Key 없이도 AWS SDK를 사용가능하다.


🧪 구현

이제 JavaScript 코드를 통해 REST API로 구현한것과 동일한 Flow의 Multipart Upload를 구현해 보자.
각 과정의 제목에 AWS SDK 공식문서의 링크를 달아놓았다.

  1. Multipart upload initiation
  2. Parts upload
  3. Multipart upload completion

1. Multipart upload initiation

const params = {
  Bucket: 'BUCKET_NAME',
  Key: 'path/example.mp4'
};
s3.createMultipartUpload(params, partUploadCallback);

/* 응답 데이터 예시
   data = {
    Bucket: "examplebucket", 
    Key: "largeobject", 
    UploadId: "ibZBv_75gd9r8lH_gqXatLdxMVpAlj6ZQjEs.OwyF3953YdwbcQnMA2BLGn8Lx12fQNICtMw5KyteFeHw.Sjng--"
   }
*/

첫번째 파라미터로 버켓이름과 Object Key를 넣어주고,

두번째 파라미터로는 initiation이 성공한 후에 실행 될 콜백 함수를 넣어준다.
필자는 이 콜백함수에 바로 2번 Parts upload하는 function을 포함시켰다.

createMultipartUpload 함수가 성공정으로 요청되면 고유한 upload id를 반환한다. 2번, 3번의 요청을 할때에 이 upload id를 사용해야 한다.

2. Parts upload

// 대충 partUploadCallback function 안의 로직

// 성공한 데이터에서 ETag 데이터를 배열에 담는 로직
// complete 판별하는 로직

const uploadPart_params = {
  Body: blob, // chunk 사이즈로 자른 blob
  Bucket: 'BUCKET_NAME',
  Key: 'path/example.mp4',
  PartNumber: part_index, // 1 ~ 10,000
  UploadId: upload_id // s3.createMultipartUpload 함수에서 받은 id
};
s3.uploadPart(uploadPart_params, partUpload); // upload next part

/* 응답 데이터 예시
   data = {
    ETag: "\"d8c2eafd90c266e19ab9dcacc479f8af\""
   }
*/

첫번째 파라미터에는 1번 함수의 반환값으로 받은 upload id와 part index 등을 포함한 파일정보들을 넣어준다.

두번째 파라미터는 콜백함수이다.
필자는 콜백함수에 다음 청크를 보내도록 자기 자신 함수를 호출하여 recursive하게 구현하였다.

uploadPart 함수는 ETag를 반환하는데, 3번의 completion에서 사용해야 하므로 배열에 담아 JsonArray 형태로 만들었다.

3. Multipart upload completion

const complete_params = {
  Bucket: 'BUCKET_NAME',
  Key: 'path/example.mp4',
  MultipartUpload: {
    Parts: eTagParts // 2번에서 담은 ETag Object의 배열
  },
  UploadId: upload_id // 1번에서 받은 upload id
};
s3.completeMultipartUpload(complete_params, completeCallback);

/* eTagParts 예시
eTagParts = [
	{
     	ETag: "\"d8c2eafd90c266e19ab9dcacc479f8af\"", 
     	PartNumber: 1
    }, 
    {
    	ETag: "\"d8c2eafd90c266e19ab9dcacc479f8af\"", 
        PartNumber: 2
    }
]
*/

/* 응답 데이터 예시
   data = {
    Bucket: "acexamplebucket", 
    ETag: "\"4d9031c7644d8081c2829f4ea23c55f7-2\"", 
    Key: "bigobject", 
    Location: "https://examplebucket.s3.<Region>.amazonaws.com/bigobject"
   }
*/

첫번째 파라미터에서는 2번과정에서 하나씩 담아두었던 ETag Object 배열을 포함한 업로드 정보를 담아준다.

두번째 파라미터는 성공이후의 로직을 실행할 콜백함수를 넣는다.

complete 함수는 해당 파일의 location, 즉 src를 반환하여, 이를 유저에게 제공해줄 수 있다.


💡참고사항

1. Abort (SDK 공식문서 링크)

REST API때와 마찬가지로, 에러발생, 유저의 업로드 취소, 페이지 이탈 등의 이유로 중단되었을 때, 버킷에 해당 업로드가 남아있는 상태이므로 Abort를 해주어야 한다.

const abort_params = {
  Bucket: 'BUCKET_NAME',
  Key: 'path/example.mp4',
  UploadId: upload_id
};
s3.abortMultipartUpload(abort_params, (err, data) => {
  if (err) console.log(err, err.stack); // an error occurred
  else console.log(data);           // successful response
});

2. 기타

  • 보안상의 이유로 접근 url을 제한하려고 버킷의 CORS 설정을 변경했다.
  • 구글링 결과, 자격증명 Pool ID는 노출된 상태에서 쓰는게 일반적이며, 보안상의 큰 문제는 없을 것이라고 한다. 다만, 보안을 강화하기 위해 IAM 정책과 BUCKET 정책에 대한 설정을 좀 더 알아 볼 필요가 있을 것 같다.
  • Chunk Size를 테스트할때는 최소인 5MB로 설정했는데, 최적의 성능과 서비스를 위한 Chunk Size는 어느정도일지 테스트가 필요하다.

Reference.
AWS JavaScript S3 SDK Doc
브라우저에서 바로 AWS S3에 파일 업로드하기

profile
going up

0개의 댓글