본문 바로가기
Programming/Node.js

Serverless를 사용해서 S3에 파일 바로 업로드 하기

by 신규하 2018. 10. 10.

Serverless file upload to S3

동작 구조

  1. 먼저 lambda 서버에 s3의 올릴 url을 요청 한다.
  2. 업로드 할 url을 받는다.
  3. 받은 s3 url에다가 파일을 보내 준다.

위와 같이 업로드를 진행 해서 자바스크립트 코드에 aws의 인증키를 노출하지 않고 S3에 바로 업로드를 진행 할 수 있다.

Serverless 설치

$ npm i serverless -g
# AWS 계정 설정하기
$ serverless config credentials --provider aws --key <ACCESS KEY ID> --secret <SECRET KEY>

Serverless 기본 프로젝트 만들기

$ serverless create --template aws-nodejs -p serverless-file-upload
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/home/gyuha/workspace/serverless-file-upload"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.30.3
 -------'
$ cd serverless-file-upload

필요한 패키지 추가해 주기

$ npm install --save aws-sdk

여기서는 s3용 주소를 받기 위해서 aws-sdk를 설치 한다.

Serverless Offline 설정 하기

개발 할 때 마다 lambda에 올려서 테스트 하기 번거롭다.. aws에서 제공하는 SAM도 있지만, docker를 사용해서 동작하는 구조라서 지나치게 느려서 serverless의 offline plugin을 사용했다.
먼저 패키지를 추가해 준다.

$ npm install serverless-offline --save-dev

serverless.yml 파일에 아래 내용을 추가 해 줍니다.

plugins:
  - serverless-offline

handler.js 파일 작성 하기

'use strict';
const AWS = require('aws-sdk');

module.exports.s3upload = async (event, context) => {
  const s3 = new AWS.S3();
  const params = JSON.parse(event.body);

  const s3Params = {
    Bucket: 'serverless-upload-qwer1',
    Key: params.name,
    ContentType: params.type,
    ACL: 'public-read'
  }

  const uploadURL = s3.getSignedUrl('putObject', s3Params)

  const response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify({
      uploadURL
    }),
  };
  return response
};

동작 테스트 하기

$ serverless offline start

로컬 서버를 띄우고.. 아래과 같이 실행해서 정상적으로 문장이 나오면 됨.

$ curl -d '{"name": "test.jpg", "type": "image/jpeg"}' -H "Content-Type: application/json" -X POST http://localhost:3000/s3upload

웹브라우저 테스트

파이썬이 깔려 있다면, 아래와 같이 간단하게 서버를 만들 수 있다.

$ python3 -m http.server 9000

s3upload.html파일을 만들고 아래 내용을 넣어 줍니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <title>파일 업로드 데모</title>
  <link rel="stylesheet" href="//fonts.googleapis.com/earlyaccess/nanumgothic.css">
  <style>
    html, body {
      height: 100%;
      margin: 0;
    }
    body {
      font-family: 'Nanum Gothic', Helvetica, Arial, sans-serif;
    }
    .aligner {
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
    }
    #drop {
      height: 100px;
      width: 200px;
      border-radius: 5px;
      color: #888;
      background-color: #eee;
      font-size: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: dotted #777;
    }
    #drop:hover {
      background-color: #fff;
    }
  </style>
</head>
<body>
  <div class="aligner">
    <div id="drop">파일을 여기로 드래그</div>
    <div>
      <h1>업로드 된 파일:</h1>
      <ul id="list">
      </ul>
    </div>
  </div>

  <script type="text/javascript">
    var drop = document.getElementById('drop');
    var list = document.getElementById('list');
    var apiBaseURL = "http://127.0.0.1:3000";
    function cancel(e) {
      e.preventDefault();
      return false;
    }
    function handleDrop(e){
      e.preventDefault();
      var dt    = e.dataTransfer;
      var files = dt.files;
      for (var i=0; i<files.length; i++) {
        var file = files[i];
        var reader = new FileReader();
        reader.addEventListener('loadend', function(e){
          fetch(apiBaseURL+"/s3upload", {
            method: "POST",
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              name: file.name,
              type: file.type
            })
          })
          .then(function(response){
            return response.json();
          })
          .then(function(json){
            return fetch(json.uploadURL, {
              method: "PUT",
              body: new Blob([reader.result], {type: file.type})
            })
          })
          .then(function(){
            var uploadedFileNode = document.createElement('li');
            uploadedFileNode.innerHTML = '<a href="//s3.amazonaws.com/slsupload/'+ file.name +'">'+ file.name +'</a>';
            list.appendChild(uploadedFileNode);
          });
        });
        reader.readAsArrayBuffer(file);
      }
      return false;
    }
    // Tells the browser that we *can* drop on this target
    drop.addEventListener('dragenter', cancel);
    drop.addEventListener('dragover', cancel);
    drop.addEventListener('drop', handleDrop);
  </script>
</body>
</html>

간단하게 아래와 같이 접속해서 파일 업로드를 테스트 해 보면 된다.

http://127.0.0.1:9000/s3upload

S3및 lambda의 권한 주기

아래는 테스트를 위해서 권한을 지나치게 주었다. 실 사용시에는 조정해서 사용해야 한다.

아래 내용은 Serverless.yml 파일 이다.

service: s3imageupload

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs8.10
  region: ap-northeast-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:*"
      Resource: "arn:aws:s3:::serverless-upload-qwer1/*"

functions:
  s3upload:
    handler: handler.s3upload
    events:
      - http:
          method: post
          path: s3upload
          cors:
            origins:
              - '*'
            headers:
              - '*'
            allowCredentials: false

resources:
  Resources:
    ImageUpload:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: serverless-upload-qwer1
        AccessControl: PublicRead
        CorsConfiguration:
          CorsRules:
          - AllowedMethods:
            - GET
            - PUT
            - POST
            - HEAD
            AllowedOrigins:
            - "*"
            AllowedHeaders:
            - "*"

S3의 BucketName은 기존에 사용하고 있는 이름이면 안 되는 경우가 있다. 중복되지 않도록 이름 짓기를 해야 한다.

배포 하기

$ sls deploy

로그 확인 하기

$ sls logs -f <function_name> --tail

위와 같이 해 놓으면 lambda의 요청에 대한 로그를 볼 수 있다.

위 내용은 github에도 올려져 있다.

참고


댓글