2017년 11월 11일 토요일

[Web] Vue.js + PWA + pre-render 프로젝트 틀 잡기 (feat. AWS S3를 이용한 웹호스팅)

Vue.js + PWA + pre-render 프로젝트 틀 잡기

(feat. AWS S3를 이용한 웹호스팅)



이번 포스팅에서는 [ Vue.js + PWA + pre-render ] 조합의 웹앱을 [ AWS S3 + CloudFront ] 조합에서 웹호스팅을 할 계획으로 Vue.js 프로젝트를 준비하는 과정을 정리해보려합니다.

0. 왜 이런 조합의 프로젝트를 준비하는가?

프로젝트를 준비하기에 앞서 제 스스로도 목적을 정리하고자 왜 위와 같은 조합을 만드는지 써보려합니다.
가볍게 의식의 흐름대로 써보자면 ㅎㅎ
Vue.js로 만든 웹페이지를 만들자!
=======================> Vue.js
그런데, 앱 같은 웹을 만들어서
우리는 웹을 만들지만 사용자는 앱을 사용하는 것처럼 느끼게 하자.
=======================> PWA(Progressive Web Apps)
이렇게 웹앱을 만들어서 AWS S3에서 웹호스팅하면,
서버 비용도 줄고 가용성도 좋으니깐, AWS S3에서 웹호스팅 하자.
=======================> AWS S3
아참! PWA를 위해서는 HTTPS를 지원해야겠다!
=======================> AWS CloudFront(with AWS ACM)
아...근데 일부 소개 페이지는 SEO도 되야겠는데..
서버가 없으니 SSR이 안되겠네..
그럼 SEO를 위한 페이지만 pre rendering을 하자!
=======================> pre-render

이런 의식의 흐름으로 [ Vue.js + PWA + pre-render ] & [ AWS S3 + CloudFront ] 조합이 탄생했습니다 ㅎㅎㅎㅎ


1. 프로젝트 시작

Vue.js webpack template 중에 PWA(Progressive Web Apps)를 위한 탬플릿이 이미 존재합니다.
아래 Github에서 해당 탬플릿을 시작할 수 있습니다.
https://github.com/vuejs-templates/pwa
해당 탬플릿의 원형은 vue webpack template이기 때문에 프로젝트 구조 등에 대한 문서는 아래 링크를 확인하면 좋습니다.
https://vuejs-templates.github.io/webpack
문서의 안내에 따라서 프로젝트를 시작할 폴더에서 아래의 명령으로 탬플릿을 다운로드 받습니다.
(단, vue-cli가 사전에 설치되어 있어야합니다.)
// 탬플릿 다운로드
$ vue init pwa my-project

// 프로젝트 폴더로 이동
$ cd my-project

// 의존 패키지 설치
$ npm install

// 개발 서버 시작
$ npm run dev
탬플릿을 다운로드 받을 때 프로젝트 설정을 위한 질문이 여러개 나오는데, 전부 디폴트로 설정해도 무방하고 조금 다른 설정을 해주고 싶은 것만 선택하시면 됩니다.
여기까지만으로도 벌써 Vue.js를 이용한 PWA 웹앱의 기반이 다져졌습니다!
여기서, 딱 두개만 수정을 하겠습니다.
(1) PWA 웹앱을 만들기 위한 manifest.json 파일에서 start_url을 아래와 같이 수정해줍니다.
// 
{
  "start_url": "/",
}
기본적으로는 /index.html로 설정되어 있는데 이것을 / 로 수정합니다.
왜냐하면, 본 포스팅에서는 PWA 웹앱을 AWS S3에 업로드하여 웹호스팅을 하고 AWS CloudFront와 연결하여 https 지원을 할 예정인데,
AWS에 PWA 웹앱을 연결하는 과정에서 이미 최초 진입 파일을 index.html로 설정하기 때문에 manifest.json에서는 시작 주소를 단지 루트(/) 경로만 표시해줘야 정상작동합니다.
(2) /build/service-worker-prod.js에서 service-worker.js 파일 위치를 수정합니다.
load 이벤트 리스너를 추가하는 부분에서 sevice-worker.js를 등록하는 코드가 아래처럼 있습니다.
navigator.serviceWorker.register('service-worker.js')
기존 처럼 되어 있으면 하위 path마다 각 path에서 service-worker.js를 찾게되고 당연히 파일이 없을거라서 등록에서 오류가 납니다.
그래서 아래처럼 파일 이름 앞에 슬래쉬를 붙여서 절대주소를 명시합니다.
navigator.serviceWorker.register('/service-worker.js')



2. 프로젝트 배포

2-1. AWS S3 + CloudFront 에 웹앱 배포

저는 AWS S3에 프로젝트를 배포하고 S3의 웹호스팅 기능으로 웹앱을 사용해 보려합니다.
AWS S3로 웹호스팅 하는 방법을 아래 링크를 참고하시면 좋습니다.
https://walkinpcm.blogspot.kr/2017/06/aws-aws-s3-static-website-hosting.html
링크의 내용에서는 HTTPS를 지원하는 내용까지 있는데,
PWA를 위해서는 웹앱이 HTTPS로 제공되어야 하기 때문에 일반적인 S3 Web Hosting 문서보다 위 문서가 도움이 되리라 생각됩니다.
배포할 파일들을 만들기 위해서 프로젝트 폴더에서 아래 명령으로 배포 파일을 준비합니다.
$ npm run build
빌드를 하고나면 프로젝트 폴더의 루트 경로에 dist 폴더가 생성됩니다.
dist폴더 내부의 모든 파일들을 S3에 업로드 하면됩니다.

2-2. SPA를 위한 AWS CloudFront 추가 설정.

SPA(Single Page Application)는 URL 주소가 변경되더라도 실제 다른 파일을 불러오는게 아니라 화면 요소만 바꿔줍니다.
그런 특징이 AWS S3 + CloudFront 조합에서는 문제점 하나를 야기합니다.
SPA에서 routing으로 URL 주소를 바꾼 상태에서(ex. '/def')
새로 고침을 하면 브라우저는 그 요청을 AWS CloudFront로 보내는데
CloudFront에는 '/def'에 대한 자원이 없어서 S3로 요청하게 되고 S3도 '/def'에 대한 자원은 없어서 결국에는 에러 페이지가 반환됩니다.
이 문제는 사용자 경험도 떨어 뜨릴 뿐 아니라 웹크롤러가 페이지를 읽지 못하는 문제이기도 합니다.
그래서 CloudFront의 에러페이지 설정에서 403, 404 에러가 발생하면 root path로 이동하도록 설정해줍니다.
설정 방법은 아래와 같습니다.
CloudFront에서 설정할 배포를 선택하고 Error Pages탭을 선탁합니다.
그리고 Create Custom Error Response를 선택합니다.

HTTP Error Code는 어떤 에러코드에 대해서 Error 처리를 할지 선택하는 것입니다.
403, 404 2개에 대해서 각각 추가해주셔야합니다.
Customize Error Response를 Yes로 선택하고,
Response Page Path에 /index.html을 기입합니다.
HTTP Response Code는 200: OK로 입력합니다.

에러코드 403, 404 대해서 위와같이 설정을 마치면 아래 처럼 에러 처리 항목 2개가 생겼을겁니다.

2-3. PWA 웹앱 확인

프로젝트 배포 파일들을 AWS S3에 업로드 하고난 뒤에 (HTTPS 까지 지원하게 한 뒤에), PWA 웹앱이라면 모바일 크롬에서 해당 페이지에 접속했을 때 하단에 홈 화면에 설치하겠냐는 배너가 나타나야 정상입니다.
하지만 설치 배너는 유저가 5분 동안 2번 방문해야 나타나는 조건이 있습니다.
개발과정에서는 그러한 조건을 충족시키기가 번거로우므로 웹앱에 브라우저로 접속했을 때 설치배너가 무조건 나타나게 해주는 설정을 합니다.
모바일 크롬에서 아래 주소를 주소창에 입력합니다.
chrome://flags/#bypass-app-banner-engagement-checks
그리고 해당 항목(bypass app banner engagement checks)를 사용으로 변경합니다.
변경하면 크롬을 다시 시작한다고 합니다. 다시 시작하고 다시 웹앱에 접속하면 설치배너가 나타납니다.
물론 설치배너를 통해서 설치 하지 않고, 크롬 메뉴에서 '홈 화면에 추가' 기능을 이용해도 똑같이 PWA 웹앱이 스마트폰에 설치되듯이 추가됩니다.


3. Pre render 플러그인 사용하기

위에서도 언급했듯이 Vue.js PWA 탬플릿은 원형이 Vue.js webpack 탬플릿이기 때문에 Vue.js webpack의 문서를 참고하면됩니다.
그리고 고맙게도 그 문서에는 Pre render에 대한 내용도 있습니다.
Vue.js에서 Pre render를 할 때는 prerender-spa-plugin를 추천합니다. Vue.js에서 공식적으로 추천하고 있습니다.
(문서: https://vuejs-templates.github.io/webpack/prerender.html)
(Github: https://github.com/chrisvfritz/prerender-spa-plugin )
사용방법은 무척 간단합니다.
(1) prerender-spa-plugin 설치
npm i prerender-spa-plugin --save-dev
(2) webpack.conf.js에 플러그인 설정
Vue.js PWA 탬플릿을 사용했다면 /build/webpack.prod.conf.js에 아래 코드들을 추가합니다.
먼저, 아래 require 문을 다른 어떤 require(또는 import)들 보다 상위에 작성합니다.
제일 상위에 위치하지 않으면 페이지가 정상적으로 표시되지 않습니다.
var PrerenderSpaPlugin = require('prerender-spa-plugin')
그리고, 아래 코드를 plugins 배열에 추가합니다.
new PrerenderSpaPlugin(
  // 컴파일된 파일들이 위치하는 폴더를 명시합니다.
  path.join(__dirname, '../dist'),
  // pre render를 할 path를 지정합니다. 콤마로 연결해서 여러 path를 기입할 수 있습니다.
  [ '/', '/abc' ]
)
(3) vue-router 의 mode를 history로 변경
prerender-spa-plugin을 사용하려면 router의 mode가 반드시 history여야합니다.
그러므로 아래와 같이 vue router의 mode를 history로 설정합니다.
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})
Vue.js PWA 탬플릿을 사용한다면, /src/router/index.js에 mode를 추가하면 됩니다.
아래는 탬플릿의 원형에 mode만 추가한 것입니다.
import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    }
  ]
})



여기까지 전부 하셨다면 Vue.js + PWA + pre render 조합의 웹앱의 기반을 잡으셨다고 할 수 있습니다.
그리고 그 웹앱을 AWS S3 + CloudFront를 이용해서 웹서버 없이 저렴하고 가용성 높게 배포 하게 되셨습니다.
혹시 참고하실 수 있으실까해서 이 포스팅을 준비하면서 연습해본 프로젝트 소스를 Github에 올려두었습니다.
아래 링크에서 참고하시면 도움이 되리라 생각됩니다.
(Github Repository 링크 : https://github.com/ChanMinPark/vue-pwa-prerender-s3-cloudfront )


이번 포스팅은 여기서 마치겠습니다.
혹여 내용이 틀리거나 수정 보완 해야하는 부분이 있다면 댓글로 남겨주세요.
감사히 받아들이고 수정, 보완하겠습니다.
감사합니다.


참고링크

2017년 8월 30일 수요일

[AWS] Express에서 동작하는 Swagger UI server 만드는 방법(AWS API Gateway, Lambda 이용)

Express에서 동작하는 Swagger UI server 만드는 방법(AWS API Gateway, Lambda 이용)

[How to make Swagger UI server on Express (with AWS API Gateway and Lambda)]

AWS의 API Gateway와 Lambda를 이용하면 간편하게 REST API를 만들 수 있습니다.
(아래 링크에 제가 예전에 정리했던 REST API 포스팅이 있습니다. 하지만, 해당 방법으로는 GET, POST, PUT, DELETE 메서드를 모두 한번에 ANY 메서드로 처리하므로 Swagger로 표현할 수 없습니다. 그러니, 참고만 하시면 좋을 것 같습니다.
https://walkinpcm.blogspot.kr/2017/05/aws-aws-api-gateway-lambda-rest-api.html )
그런데, 만든 API에 대한 정보를 다른 사람에게 알려야할 때 API Gateway에 접속해서 보라고 하기에는 비효율적이고 한눈에 알아보기도 쉽지 않습니다.
즉, API Gateway로 만든 API도 문서화가 필요합니다. API문서화는 여러 방법이 있는데, 본 포스팅에서는 Swagger UI를 이용해서 API Document Web Page를 만들어보겠습니다.

0. 구조


Swagger UI는 다양한 서버에서 동작할 수 있는데, 여기서는 node 환경에서 express 웹서버로 Swagger UI를 구성하겠습니다.
그리고 해당 express 웹서버를 AWS Lambda에서 구동시키고, API Gateway를 이용해서 접속 URL을 획득해서 브라우저에서 접속할 수 있게 구성할 것입니다.
Swagger UI는 json 또는 yaml로 작성된 API 정의 문서를 보기 좋게 GUI로 보여주는데, 본 포스팅에서는 json 파일을 AWS S3에 업로드해서 이용합니다.

1. 필요한 것들

yarn과 Node js 설치는 일반적인 사항이므로 여기서는 다루지 않겠습니다.
또한, API Gateway + Lambda + Express 로 웹 어플리케이션을 Lambda에서 구동하는 방법은 아래 링크를 참고해주시기 바랍니다. 여기서는 코드만 바로 작성하도록 하겠습니다.
( 링크 : https://walkinpcm.blogspot.kr/2017/08/awsaws-lambda-express-react-application.html )

2. 프로젝트 준비

프로젝트를 진행할 폴더 내에서 아래 작업들을 진행합니다.
우선 yarn 프로젝트를 초기화 합니다.
entry point는 lambda.js로 해주세요. 중요한건 아닌데.. 여기서는 index.js는 만들지도 않을 거에요.
yarn init
프로젝트 초기화가 되면 아래 명령으로 필요한 node 패키지들을 설치합니다.
기본적으로 이렇게 3개만 있으면 됩니다.
yarn add express swagger-ui-express aws-serverless-express

3. 소스파일 작성

2개의 파일이 필요합니다.
  • lambada.js
    • AWS Lambda에 프로젝트를 업로드 했을 때, API Gateway를 통해서 들어온 요청을 받아서 express로 전달할 파일입니다.
  • app.js
    • express가 동작하는 파일입니다.
lambda.js의 코드는 AWS Lambda에서 express 어플리케이션을 구동시키기 위해서 사용하는 일반적인 코드입니다.
// lambda.js
'use strict';

const awsServerlessExpress = require('aws-serverless-express');
const app = require('./app');
const server = awsServerlessExpress.createServer(app);

exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);
// app.js
'use strict';

const express = require('express');
const app = express();

const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware');

// swagger-ui-express 미들웨어를 불러옵니다.
const swaggerUi = require('swagger-ui-express');

app.use(awsServerlessExpressMiddleware.eventContext());

// 아래 처럼 작성하면 {API Gateway에서 획득한 주소}/api-docs/v1 으로 Swagger UI web page에 접속 할 수 있습니다.
// Swagger Json 파일은 AWS S3에 업로드 하고 해당 객체에 주어지는 URI를 6번째 인자에 복사해 넣습니다.
app.use('/api-docs/v1', swaggerUi.serve, swaggerUi.setup(null, null, null, null, null
    , '{Swagger Json 파일의 S3 주소}'
));

module.exports = app;

여기까지 진행하면 프로젝트 구조가 아래와 같습니다.
Project Folder
├── node_modules
├── app.js
├── lambda.js
├── package.json
└── yarn.lock

4. Swagger Json 파일을 AWS S3에 업로드

Swagger Json 파일을 AWS S3에 업로드 합니다.
혹시, Json 파일을 직접 작성하시기 전에 예제를 이용해서 테스트 해보고 싶으시다면, S3에 올리는 단계를 건너 뛰시고
Json 파일의 URL을 http://petstore.swagger.io/v2/swagger.json로 사용하시면 좋습니다.
해당 json 파일은 swagger-ui-express에서 제공하는 예제입니다.
S3에 Json 파일을 업로드 하면, 업로드 후에 2가지 작업을 해줘야합니다.
  • 첫번째, json 파일의 접근 권한을 public으로 설정해줘야합니다. 요즘엔 파일을 업로드하고 파일의 '개요'를 보면 '퍼블릭으로 설정' 기능이 있어서 간편하게 public으로 설정이 됩니다.
  • 두번째, json 파일을 업로드한 S3 Bucket의 CORS에 header를 추가합니다. Bucket의 '권한'에서 'CORS 구성'을 아래와 같이 작성합니다. 기본 작성된 것에 딱 한줄 추가됩니다.
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
    <AllowedHeader>Content-Type</AllowedHeader>      <!-- <<== 이거 한줄 추가합니다. -->
</CORSRule>
</CORSConfiguration>

5. 프로젝트를 AWS Lambda에 올리고 API Gateway와 연결하기

이 단계는 아래 링크를 참고해주세요.
이 단계만 해도 글이 어마어마하게 길어져서 이전의 포스팅을 안내해드립니다.
제 포스팅 뿐만아니라 제 포스팅을 시작으로 관련 키워드들을 많이 검색해서 자료를 보시길 추천드립니다.
( 링크 : https://walkinpcm.blogspot.kr/2017/08/awsaws-lambda-express-react-application.html )
핵심적인 사항은 아래와 같습니다.
  • Lambda Function을 만들 때, 프로젝트 파일들을 zip으로 압축해서 파일로 Lambda Function에 업로드하고 구성 설정에서 Handler를 lambda.handler로 해야합니다.
  • API Gateway는 아래와 같은 구조로 /{proxy+} 만 만들어주시면 됩니다. CORS도 설정해주시구요.
/
    /{proxy+}
        ANY
        OPTIONS

6. Swagger UI Web Page 접속

제대로 작업이 진행되었다면, 아래와 같이 Swagger UI 화면이 나타납니다.
접속 주소는 API Gateway에서 배포한 후에 생성된 URL에 위에서 설정했던 /api-docs/v1을 붙인 것입니다.



이상으로 Swagger UI Web Page를 만드는 방법을 끝마치겠습니다.
혹시 잘 안되거나 잘못 된 부분이 있다면 댓글 남겨주세요. 최대한 빠르게 확인하고 답변 또는 포스팅 수정/보완 하도록 하겠습니다.
감사합니다~

Tip.
아래 링크에서는 Json 또는 Yaml로 된 Swagger 문서를 바로바로 Swagger UI로 볼 수 있습니다. Swagger 문서를 작성할 때 좋습니다.
(링크 : http://editor.swagger.io )
제 경우에는 AWS API Gateway로 API를 작성했기 때문에 API Gateway의 기능인 '내보내기' 기능으로 Swagger 문서를 획득할 수 있어서 위 링크에 복사해두고 조금 더 다듬는 용도로 사용합니다.

2017년 8월 27일 일요일

[AWS] aws-serverless-express에서 binary(이미지 파일) 지원하는 방법

aws-serverless-express에서 binary(이미지 파일) 지원하는 방법

[How to support binary(image file) in aws-serverless-express]
지난 포스팅에서 AWS Lambda에서 aws-serverless-express를 이용하고 AWS API Gateway와 연결하여 웹 어플리케이션을 서버리스(Serverless)로 서비스하는 방법을 정리해보았습니다.
( 링크 : https://walkinpcm.blogspot.kr/2017/08/awsaws-lambda-express-react-application.html )
그런데, 위 포스팅의 절차대로만 했을때 이미지 파일이 화면에 그려지지 못하는 문제가 있었습니다.
개발자도구를 이용해서 확인해보면 분명 파일을 받아오는 요청은 200 OK 를 받지만 이미지가 그려지지 않았습니다.
알아보니 API Gateway와 Lambda에서는 기본적으로는 binary를 지원하지 않았던게 문제였습니다.
그래서 이번 포스팅에서는 그 문제를 해결하는 방법을 정리합니다.

0. 해결방법

해결방법은 단순합니다.
API Gateway와 Lambda(aws-serverless-express 사용부분)에서 binary를 지원하도록 설정해주면 되었습니다.
단, 두 군데 모두 설정이 되어야 제대로 binary를 지원합니다.

1. API Gateway 이진 지원 설정

API Gateway에서 사용할 API를 선택하면 좌측 하위 메뉴에 '이진 지원' (Binary Support)가 있습니다.
해당 메뉴를 클릭하고 '이진 미디어 형식' (Binary media types)에 아래 세 항목을 각각 추가합니다.
(jpg와 png 파일을 지원하고자 하여 아래 image/jpeg와 image/png를 입력합니다. 필요에 따라 다른 항목을 추가하시면 됩니다.)
image/jpeg
image/png
*/*

2. aws-serverless-express 코드에서 이진 형식 설정

기본적으로 aws-serverless-express를 이용해서 express 어플리케이션을 Lambda에서 구동시킬때는 아래와 같은 코드가 작성됩니다.
(자세한 코드는 맨 위에 링크된 지난 포스팅을 참고해주세요.)
const server = awsServerlessExpress.createServer(app);
binary를 지원하기 위해서는 createServer()를 호출할 때 지원할 binary가 무엇인지 알려줘야합니다.
코드는 아래와 같이 수정됩니다.
const binaryMimeTypes = [
    'image/jpeg',
    'image/png'
];

const server = awsServerlessExpress.createServer(app, null, binaryMimeTypes);


끝났습니다ㅎㅎㅎ
이렇게 간단하게 해결될 것을 저는 이래저래 헤맷네요..
그리고 항상 API Gateway는 배포를 해줘야 적용이 됩니다 ㅎㅎ
이 글을 쓰면서도 배포를 빼먹어서 잠시 또 헤맷네요..ㅎㅎㅎ
이상입니다. 감사합니다.

2017년 8월 20일 일요일

[AWS]AWS Lambda에 Express 위에서 작동하는 React Application 업로드(배포) 하는 방법

AWS Lambda에 Express 위에서 작동하는 React Application 업로드(배포) 하는 방법

[How to upload(deploy) React application on Express to AWS Lambda]
(부제: aws-serverless-express 사용 방법)
주의! 본 글은 AWS Lambda와 API Gateway의 사용법은 구체적으로 다루지 않습니다. 이에 대해서는 사전에 익히고 보셔야 좋습니다.

최근 AWS에서는 Serverless Architecture를 지원해주기 위해 노력하고 있는 것 같습니다.
그 일환으로 aws-serverless-express를 이용해서 AWS Lambda에 Node.js 진영의 웹 프레임워크(Web Framework)인 Express 기반의 Web Application을 배포할 수 있게 했습니다.
또한, Serverless 기반의 App을 개발하고 배포하기 편리하게 하는 serverless application model(SAM) 을 공개했습니다.
SAM은 아직 Beta라서 그런지 안정적이지는 않은 것 같습니다. SAM을 이용하면 로컬에서 Lambda에 배포한 것 같이 테스트 할 수 있고 좋은데.. 조금만 더 기다려봐야할 것 같습니다.

이번 포스팅에서는 aws-serverless-express를 이용해서 React App을 AWS Lambda에 배포하는 방법을 정리해보려 합니다. 


1. 준비

Yarn과 Node.js는 일반적인 사항이라 따로 설치 방법을 정리하지는 않겠습니다.

1-1. React-Starter-Kit

본 포스팅에서는 React App의 뼈대를 잡기 위해서 React-Starter-Kit을 사용합니다. React-Starter-Kit은 Express기반으로 작성되어 있으며, Universal-Router를 이용해서 Server-side rendering을 지원합니다.
Terminal(또는 Powershell 등)에서 아래 명령을 수행하여 공식 Github에서 프로젝트를 내려 받습니다.
아래 명령에서 마지막에 있는 MyApp은 프로젝트 소스를 내려받을 폴더 이름입니다. 그러니 각자가 원하는 이름으로 지정하시면 됩니다.
git clone -o react-starter-kit -b master --single-branch https://github.com/kriasoft/react-starter-kit.git MyApp
프로젝트 폴더 내부로 이동해서 아래 명령으로 package.json에 명시된 dependencies를 설치합니다.
cd MyApp
yarn install

1-2. aws-serverless-express

aws-serverless-express는 lambda에서 express를 작동 시킬 수 있게 해주는 미들웨어 패키지입니다.
아래 명령으로 프로젝트에 모듈을 추가해줍니다.
yarn add aws-serverless-express

이제 준비는 되었습니다. 다음으로 소스코드의 수정 및 작성을 정리하겠습니다. 


2. Lambda에서 작동시키기 위한 소스 코드 수정 및 추가 작성

기존 Express Application은 listen() 함수를 이용해서 포트를 열고 클라이언트의 접속을 대기합니다.
하지만, Lambda에서는 클라이언트의 요청을 listen()으로 받는게 아니라 aws-serverless-express가 받아서 express로 전달합니다.
이를 위해서 코드의 수정이 필요합니다.

2-1. React-Starter-Kit의 소스 코드 수정

위에서 언급했듯이 listen() 함수가 필요가 없고, aws-serverless-express가 클라이언트 요청을 받도록 수정해줘야합니다.
이를 수정하기 위해서, src/server.js 에서 아래 부분을 주석처리하거나 삭제합니다.
const promise = models.sync().catch(err => console.error(err.stack));
if (!module.hot) {
  promise.then(() => {
    app.listen(config.port, () => {
      console.info(`The server is running at http://localhost:${config.port}/`);
    });
  });
}
그리고, aws-serverless-express 패키지를 import 합니다.
다른 패키지들이 import된 곳의 맨 마지막에 import하면 됩니다.
import awsServerlessExpressMiddleware from 'aws-serverless-express/middleware';
그리고 다른 미들웨어들이 작성되어 있는 곳에 aws-serverless-express 미들웨어도 추가해 줍니다.
//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(awsServerlessExpressMiddleware.eventContext());         // <--- 추가 됨.
app.use(express.static(path.resolve(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
아래 수정은 Lambda에서 제대로 작동하지 않아서 internal error를 발생시키는 패키지를 제거하는 것입니다.
아래 부분들을 찾아서 주석처리 해주시면 됩니다.
아래 패키지 들이 무조건 Lambda에서 사용을 못하는지는 아직 깊게 파보지 않아서 모르겠으나, 당장은 필요 없어서 일단 제거합니다.(앞으로 각 패키지들도 사용하는 방법을 연구해볼 예정입니다.)
import expressGraphQL from 'express-graphql';
import passport from './passport';
import models from './data/models';
import schema from './data/schema';

...

app.use(passport.initialize());

...

app.get(
  '/login/facebook',
  passport.authenticate('facebook', {
    scope: ['email', 'user_location'],
    session: false,
  }),
);
app.get(
  '/login/facebook/return',
  passport.authenticate('facebook', {
    failureRedirect: '/login',
    session: false,
  }),
  (req, res) => {
    const expiresIn = 60 * 60 * 24 * 180; // 180 days
    const token = jwt.sign(req.user, config.auth.jwt.secret, { expiresIn });
    res.cookie('id_token', token, { maxAge: 1000 * expiresIn, httpOnly: true });
    res.redirect('/');
  },
);

...

app.use(
  '/graphql',
  expressGraphQL(req => ({
    schema,
    graphiql: __DEV__,
    rootValue: { request: req },
    pretty: __DEV__,
  })),
);

2-2. lambda.js 코드 추가 작성

lambda.js는 React-Starter-Kit에 포함되어 있는 파일이 아닙니다.
Lambda와 프로젝트 파일들을 이어주는 접점이라고 생각하시면 됩니다.
파일은 프로젝트 폴더 최상위 위치에 저장해두면 나중에 계속 복사하면 되서 편합니다.
// lambda.js
'use strict';

const awsServerlessExpress = require('aws-serverless-express');
const app = require('./server');
const server = awsServerlessExpress.createServer(app.default);

exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);
여기서 주의할 점은, awsServerlessExpress.createServer()의 인자로 그냥 app이 아니라 app.default가 들어간다는 것입니다.
왜냐하면 빌드 된 server.js는 webpack에 의해서 여러 패키지가 하나로 합쳐져 있어서 export된 모듈이 많은데 그 중에서 원하는 것은 default 로 export된 모듈이기 때문입니다.


3. 프로젝트 빌드

이제 Lambda에 파일들을 배포하기 위해서 프로젝트를 빌드합니다.
가운데 -- 만 있는거 꼭 넣어줘야한다고 합니다.
yarn run build -- --release
빌드 명령을 수행하고 나면 build 폴더가 생성됩니다.
이 build 폴더에 바로 전에 작성한 lambda.js 파일을 복사해서 넣어줍니다.

그러고나서, build폴더 내부에서 yarn 명령으로 dependencies를 설치해줍니다.

cd build
yarn

4. build 폴더 압축(zip)

이제 build 폴더를 zip 파일로 압축해서 Lambda에 배포할 수 있게 준비합니다.
어떤 압축 프로그램을 해도 좋지만 확장자가 .zip으로 되어야하고,
압축 파일을 열었을 때 build 폴더가 보이는게 아니라 build 폴더의 내부 파일들이 최상위에 바로 보이게 압축해야합니다.

5. AWS Lambda에 zip 파일 업로드

AWS Lambda에 Function을 생성하고 코드를 압축 파일 업로드 방식으로 선택해서 방금 압축한 zip 파일을 업로드합니다.
그리고 Configuration(구성)에서 handler(핸들러)를 lambda.handler로 설정해줍니다.

6. API Gateway

API Gateway를 이용해서 Lambda에 올린 React App에 접속합니다.
API 구조는 아래 그림과 같이 구성합니다. 루트(/)에 Any method를 둔 것은 Web Application의 홈 화면을 접근하기 위해서이고, /{proxy+}는 루트 디렉토리 이외의 페이지에 접근하기 위해 필요합니다.


7. 확인

API Gateway 구성을 마쳤으면 할당된 URL을 이용해서 아래 주소로 접속해 봅니다.
https://{할당된 주소}/about
본 글에서 사용한 프로젝트에서는 몇몇 코드를 지워서 그런지 홈화면이 없고 Error 페이지가 나타납니다. 그래서 about 페이지에 접근하여 정상동작을 확인합니다.
about 페이지가 정상적으로 나타나면 성공입니다.

마무리

API Gateway에서 '사용자 지정 도메인 이름'을 사용하면 원하는 도메인으로 Lambda에 올린 Web Application에 접속할 수 있습니다. 어렵지 않으니 금방 하실거에요.
개인적으로 Lambda에 Web Application을 올릴 수 있는게 너무 좋습니다. EC2에서 서비스 한다면 가용성을 높이기 위해 다수의 EC2로 서비스하고 ELB로 로드밸런싱도 걸어줘야 할텐데, Lambda에 올리면 그런 수고가 덜어지니 너무 편합니다.
React Web App을 SSR 지원없이 서비스 한다면 S3에서 웹 호스팅으로 서비스 해도 될 테지만, Application 성격에 따라 SSR이 꼭 지원되야하면 Lambda가 너무 좋은 옵션인 것 같습니다.
앞으로도 Serverless로 새로 알게되는 정보들이 있으면 열심히 정리해보겠습니다.
감사합니다.

참고 링크