S3 부가 기능 (P226)
버전 관리
- 객체를 덮어 쓴 경우, 이전 버전으로 돌아갈 수 있음
- 버전 관리 기능은 활성화해서 사용
- 버전 관리를 해제할 수는 없음 ⇒ 일시 중지는 가능
정적 웹 사이트 호스팅
- 서버 측 코드 실행은 지원하지 않음
- HTML, CSS, Image, JavaScript와 같은 정적인 컨텐츠를 제공(서비스)
스토리지 클래스
중복성, 액세스 특징 및 가격 정책에 따라 네 가지 스토리지 클래스를 지원
요금은 저장 비용(스토리지 클래스, 리전, 저장된 데이터의 양)과 요청 비용(요청과 데이터 전송)으로 구분
- Standard
- 데이터에 자주 액세스할 때 적합
- 99.999999999%의 내구성을 지원
- 저장 비용 → 10,000건의 요청당 $0.004
- 사용 비용 → 처음 1TB의 데이터/월 사용 시 GB 당 $0.0300
- Standard_IA(Infrequent Access)
- 자주 액세스하지 않는 데이터에 적합
- Glacier
- 간헐적으로 접근
- 데이터를 가져오는데 3~5시간 소요
- 처음부터 Glacier 클래스로 객체를 생성할 수 없음 ⇐ 생명주기 관리 규칙을 사용해서 Glacier로 전환
- Reduced Redundancy
- 99.99% 내구성 지원
웹 페이지를 통해 인증된 사용자만 S3 버킷에 파일을 업로드할 수 있도록 제한 (P238)


Lambda 함수 ⇒ 사용자를 확인하고 S3에 파일을 업로드하는데 필요한 정책과 서명을 생성
#1 upload-s3 사용자 생성 및 업로드 정책 생성




{
"Version": "2012-10-17",
"Statement": [
{
"Resource": "arn:aws:s3:::serverless-video-upload-myanjini", ⇐ 업로드 버밋의 ARN
"Action": "s3:ListBucket",
"Effect": "Allow"
},
{
"Resource": "arn:aws:s3:::serverless-video-upload-myanjini/*",
"Action": "s3:PutObject",
"Effect": "Allow"
}
]
}



#2 람다 함수 생성 및 배포
#2-1 작업 디렉터리 생성
PS C:\Users\i> cd C:\serverless\
PS C:\serverless> mkdir get-upload-policy
PS C:\serverless> cd .\get-upload-policy\
PS C:\serverless\get-upload-policy> npm init -y
#2-2 모듈 설치
PS C:\serverless\get-upload-policy> npm install -y async
PS C:\serverless\get-upload-policy> npm install -y crypto-js
#2-3 package.json 파일에 create, precreate 스크립트를 작성
{
"name": "get-upload-policy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"create": "aws lambda create-function --function-name get-upload-policy --handler index.handler --memory-size 128 --runtime nodejs12.x --role arn:aws:iam::199503606661:role/lambda-s3-execution-role --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip",
"precreate": "zip -r Lambda-Deployment.zip * -x *.zip *.log node_modules/aws-sdk/*"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"async": "^3.2.0",
"crypto-js": "^4.0.0"
}
}
#2-4 람다 함수 작성
c:\serverless\get-upload-policy\index.js
// P242
'use strict';
var async = require('async');
var crypto = require("crypto-js");
// 상수를 정의
const C_ACL = 'private';
const C_NOW = new Date().toISOString(); // 현재 시간 2021-03-19T02:04:38.030Z
const C_DATE_STAMP = C_NOW.slice(0,10).replace(/-/g,''); // 현재 시간을 YYYYMMDD 형식으로 변환 2021-03-19 => 20210319
const C_REGION_NAME = 'us-east-1';
const C_SERVICE_NAME = 's3';
const C_X_AMZ_DATE = C_NOW.replace(/[-:\.]/g,''); // 20210319T020438030Z
const C_X_AMZ_ALGORITHM = 'AWS4-HMAC-SHA256';
const C_X_AMZ_CREDENTIAL = `${process.env.ACCESS_KEY}/${C_DATE_STAMP}/${C_REGION_NAME}/${C_SERVICE_NAME}/aws4_request`;
// 반환할 오류 메시지 포맷을 정의해서 반환하는 함수
function createErrorResponse(errCode, errMessage) {
var response = {
'statusCode': errCode,
'headers': { 'Access-Control-Allow-Origin': '*' },
'body': JSON.stringify({ 'error': errMessage })
};
return response;
}
// 반환할 성공 메시지 포맷을 정의해서 반환하는 함수
function createSuccessResponse(message) {
var response = {
'statusCode': 200,
'headers': { 'Access-Control-Allow-Origin': '*' },
'body': JSON.stringify(message)
};
return response;
}
// expiration(정책 유효 기간)을 계산해서 반환하는 함수
// 다음 날을 ISO 형식으로 반환
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate
function generateExpirationDate() {
var currentDate = new Date();
currentDate = currentDate.setDate(currentDate.getDate() + 1);
// ~~~~~~~~~~~~~~~~~~~~~~~~~
// 현재 일자 + 1 => 다음 날
return new Date(currentDate).toISOString();
}
// 보안정책문서를 생성
function generatePolicyDocument(filename, next) {
var expiration = generateExpirationDate(); // 정책 유효기간
var dir = Math.floor(Math.random()*10**16).toString(16);// 디렉터리 명으로 사용할 난수를 생성
var key = dir + '/' + filename; // 키 이름을 설정
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
var policy = {
'expiration': expiration,
'conditions': [
{ acl: `${C_ACL}` },
{ bucket: process.env.UPLOAD_BUCKET },
[ 'starts-with', '$key', `${dir}/` ], // 키 이름이 ${dir}/ 형식으로 시작해야 함
{ 'x-amz-algorithm': `${C_X_AMZ_ALGORITHM}`},
{ 'x-amz-credential': `${C_X_AMZ_CREDENTIAL}` },
{ 'x-amz-date': `${C_X_AMZ_DATE}` }
]
};
next(null, key, policy); // waterfall 함수에 따라 encode 함수가 호출
}
// 보안정책문서의 포맷을 변경 : 문자열 -> JSON -> 개행문자를 제거 -> BASE64로 인코딩
function encode(key, policy, next) {
var json = JSON.stringify(policy).replace('\n', '');
// ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
// JSON 형식으로 변환 개행문자를 제거
var encodedPolicy = new Buffer(json).toString('base64');
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BASE64로 인코딩
next(null, key, encodedPolicy); // waterfall 함수에 따라 sign 함수를 호출
}
// 서명 키를 생성
// https://docs.aws.amazon.com/ko_kr/general/latest/gr/signature-v4-examples.html
function getSigningKey() {
var dateKey = crypto.HmacSHA256(C_DATE_STAMP , "AWS4" + process.env.SECRET_ACCESS_KEY);
var dateRegionKey = crypto.HmacSHA256(C_REGION_NAME , dateKey);
var dateRegionServiceKey = crypto.HmacSHA256(C_SERVICE_NAME, dateRegionKey);
var signingKey = crypto.HmacSHA256("aws4_request", dateRegionServiceKey);
return signingKey;
}
// 서명을 생성
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
function sign(key, encodedPolicy, next) {
var signingKey = getSigningKey();
var signature = crypto.HmacSHA256(encodedPolicy, signingKey);
next(null, key, encodedPolicy, signature);
}
exports.handler = function(event, context, callback) {
var filename = null;
if (event.queryStringParameters && event.queryStringParameters.filename) {
filename = decodeURIComponent(event.queryStringParameters.filename);
} else {
callback(null, createErrorResponse(500, '파일명이 누락되었습니다.'));
return;
}
async.waterfall([ async.apply(generatePolicyDocument, filename), encode, sign ], function (err, key, encoded_policy, signature) {
if (err) {
callback(null, createErrorResponse(500, err));
} else {
var result = {
upload_url: process.env.UPLOAD_URI,
encoded_policy: encoded_policy,
key: key,
acl: `${C_ACL}`,
x_amz_algorithm: `${C_X_AMZ_ALGORITHM}`,
x_amz_credential: `${C_X_AMZ_CREDENTIAL}`,
x_amz_date: `${C_X_AMZ_DATE}`,
x_amz_signature: `${signature}`
};
callback(null, createSuccessResponse(result));
}
});
}
#2-5 람다 함수 생성 및 배포
PS C:\serverless\get-upload-policy> npm run create

#2-6 람다 함수 실행에 필요한 환경변수를 설정

ACCESS_KEY, SECRET_ACCESS_KEY ⇒ upload-s3 사용자의 액세스 키 ID, 비밀 액세스 키

UPLOAD_URI ⇒ https://업로드버킷이름.s3.amazonaws.com
UPLOAD_BUCKET ⇒ 업로드 버킷 이름

#2-7 람다 함수 테스트

{
"queryStringParameters": {
"filename": "myvideo.mp4"
}
}

#3 API Gateway 설정
#1 24-hour-video API 생성



#2 s3-policy-document 리소스 생성


#3 GET 메소드 생성


#4 API 배포



#4 웹 사이트에 업로드 기능을 추가
c:\serverless\24-hour-video\index.html
<div class="container" id="video-list-container">
<div id="video-template" class="col-md-6 col">
<div class="video-card">
<!-- 동영상을 재생해 주는 요소(태그) -->
<video width="100%" height="100%" controls autoplay>
<!-- video 태그에 사용될 동영상의 소스 -->
<!-- src 속성에 동영상의 주소를 추가하면 재생이 가능 -->
<source type="video/mp4">
지원하지 않는 타입
</video>
</div>
</div>
<div calss="row">
<!-- S3 버킷에 저장된 동영상의 목록을 제시하고
동영상 이름을 클릭하면 video 태그를 통해서 동영상 재생 -->
<ul id="video-list">
</ul>
</div>
</div>
<!-- 시작: P249 참고 업로드 버튼 및 진행률 표시 (대략 94라인) -->
<span id="upload-video-button" class="btn btn-info btn-file">
<span class="glyphicon glyphicon-plus"></span>
<input id="upload" type="file" name="file"><!-- 파일 선택 창 -->
</span>
<div class="progress" id="upload-progress">
<div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100"></div><!-- 진행률 표시 -->
</div>
<!-- 끝 -->
c:\serverless\24-hour-video\js\upload-controller.js
var uploadController = {
data: {
config: null
},
uiElements: {
uploadButton: null
},
init: function(configConstants) {
this.data.config = configConstants;
this.uiElements.uploadButton = $('#upload');
this.uiElements.uploadButtonContainer = $('#upload-video-button');
this.uiElements.uploadProgressBar = $('#upload-progress');
this.wireEvents();
},
wireEvents: function() {
var that = this;
// 파일 선택창의 내용이 변경된 경우 수행할 기능을 정의
this.uiElements.uploadButton.on('change', function(result) {
// 선택한 파일 정보를 가져와서 변수에 할당
// file = {name: "수행평가 - 1조.pdf", lastModified: 1616109336996, lastModifiedDate: Fri Mar 19 2021 08:15:36 GMT+0900 (대한민국 표준시), webkitRelativePath: "", size: 5272297, …}
var file = $('#upload').get(0).files[0];
// 24-hour-video API의 s3-policy-document 리소스에 filename 파라미터의 값으로 선택한 파일의 이름을 전달
var requstDocumentUrl = that.data.config.getUploadPolicyApiUrl + '/s3-policy-document?filename=' + encodeURI(file.name);
$.get(requstDocumentUrl, function(data, status) {
// file: 선택한 파일 정보
// data: get-upload-policy 람다 함수의 실행 결과 = S3 버킷 업로드 정책 문서
that.upload(file, data, that);
});
});
},
// 파일 선택창에서 선택한 파일을 S3 버킷으로 업로드
upload: function(file, data, that) {
// 파일 선택창을 숨기고, 진행률 창을 보이게 처리
this.uiElements.uploadButtonContainer.hide();
this.uiElements.uploadProgressBar.show();
this.uiElements.uploadProgressBar.find('.progress-bar').css('width', '0');
// 요청 본문을 생성
var fd = new FormData();
fd.append('key', data.key);
fd.append('policy', data.encoded_policy);
fd.append('acl', data.acl);
fd.append('x-amz-algorithm', data.x_amz_algorithm);
fd.append('x-amz-credential', data.x_amz_credential);
fd.append('x-amz-date', data.x_amz_date);
fd.append('x-amz-signature', data.x_amz_signature);
fd.append('file', file, file.name);
$.ajax({
url: data.upload_url,
type: 'POST',
data: fd,
processData: false,
contentType: false,
xhr: this.progress,
// ajax 요청을 전달하기 전에 수행해야 할 작업을 명시
// Authorization 요청 헤더의 값을 제거
beforeSend: function(req) {
req.setRequestHeader('Authorization', '');
}
})
// 업로드에 성공한 경우 수행할 내용
.done(function(response) {
that.uiElements.uploadButtonContainer.show();
that.uiElements.uploadProgressBar.hide();
alert('업로드 성공');
})
// 업로드에 실패한 경우 수행할 내용
.fail(function(response) {
that.uiElements.uploadButtonContainer.show();
that.uiElements.uploadProgressBar.hide();
console.error(response);
alert('업로드 실패');
})
},
// 진행율을 표시
progress: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(evt) {
var percentage = evt.loaded / evt.total * 100;
$('#upload-progress').find('.progress-bar').css('width', percentage + '%');
};
return xhr;
}
}
c:\serverless\24-hour-video\js\config.js
var configConstants = {
auth0: {
domain: 'naanjini.us.auth0.com',
clientId: 'BAdEeZTkFlAdyC1a7nWENB3sRydpByXR'
},
// user-profile API 호출 URL
apiBaseUrl: 'https://vfhp67njsk.execute-api.us-east-1.amazonaws.com/dev',
// get-file-list API 호출 URL
getFileListApiUrl: 'https://6ln109dh77.execute-api.us-east-1.amazonaws.com/dev',
// 추가 get-upload-policy API 호출 URL
getUploadPolicyApiUrl: 'https://ehw3hrbl7a.execute-api.us-east-1.amazonaws.com/dev'
};
c:\serverless\24-hour-video\js\main.js
// 즉시 실행 함수
(function() {
// 해당 웹 페이지 문서가 로딩되면 설정 정보를 가져와서 설정
$(document).ready(function() {
// user-controller.js에 선언되어 있는 userController 객체의 init 메소드를 호출
// coonfig.js에 선언되어 있는 configConstants 객체를 인자로 전달
userController.init(configConstants);
videoController.init(configConstants);
uploadController.init(configConstants); /* 추가 */
});
})();
c:\serverless\24-hour-video\index.html
<!-- (생략) -->
<script src="https://cdn.auth0.com/js/lock/11.27/lock.min.js"></script>
<script src="js/config.js"></script>
<script src="js/user-controller.js"></script>
<script src="js/video-controller.js"></script>
<script src="js/upload-controller.js"></script><!-- 추가 -->
<script src="js/main.js"></script>
<!-- (생략) -->
c:\serverless\24-hour-video\css\main.css
/* 업로드 버튼 및 진행률 표시를 위한 스타일을 추가 */
#upload-video-button {
display: none;
margin-bottom: 30px;
}
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
#upload-progress {
display: none;
}
#video-list-container {
text-align: center;
padding: 30px 0 30px;
}
.progress {
background: #1a1a1a;
margin-top: 6px;
margin-bottom: 36px;
}
#6 파일 업로드 테스트

Access to XMLHttpRequest at 'https://ehw3hrbl7a.execute-api.us-east-1.amazonaws.com/dev/s3-policy-document?filename=%EC%88%98%ED%96%89%ED%8F%89%EA%B0%80%20-%205%EC%A1%B0.pptx' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
⇒ 24-hour-video API의 CORS 활성화 후 재배포




⇒ AccessToken 헤더를 허용하도록 CORS 설정하지 않아서 CORS 오류가 발생



"업로드 실패" ⇒ 파일을 S3 버킷에 업로드했을 때 나오는 메시지 ⇒ s3-upload-policy 람다 함수 호출(실행)은 완료되었음을 의미
Access to XMLHttpRequest at 'https://s3.amazonaws.com/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
⇒ S3 버킷에 CORS 설정이 필요
#5 업로드 S3 버킷에 CORS 설정
참고 ⇒ https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/cors.html



[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"POST"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
#7 파일 업로드 테스트


실습 리소스 정리
EC2



볼륨 ⇒ 볼륨 분리 → 볼륨 삭제




종료된 인스턴스 1개와 기본 보안 그룹 1개만 보여야 함
Elastic Transcoder 파이프라인 삭제 확인
S3 버킷 삭제
버킷을 비운 후 삭제

API Gateway
API 삭제

Lambda

CloudWatch


IAM




DevStack 설치
https://myanjini.tistory.com/entry/DevStack-%EC%84%A4%EC%B9%98
'CLOUD > AWS' 카테고리의 다른 글
3/19 - AWS 마무리 정리 (0) | 2021.03.19 |
---|---|
3/17 - AWS 14차시 (1) | 2021.03.17 |
3/16 - AWS 13차시 (0) | 2021.03.16 |
3/15 - AWS 12차시 (0) | 2021.03.15 |
3/12 - AWS 11차시 (0) | 2021.03.12 |