안경잡이개발자

728x90
반응형

  이번 시간에는 AWS Lambda와 MongoDB를 활용하여 하나의 게시물(Board) API를 완전히 개발해보는 시간을 가져보도록 하겠습니다. 따라서 가장 먼저, 다음과 같이 /board 경로에 GET, POST, PUT, DELETE 메소드의 API를 Gateway로 열어줍니다. 그리고 /board/{proxy+} 경로의 모든 메소드를 사용할 수 있도록 합니다.

 

  /board/{proxy+}로 설정하면, 모든 Path Parameter를 받을 수 있게 됩니다.

 

 

  이후에 소스코드를 작성합니다. 우리가 작성할 게시물 Rest API의 기본적인 명세는 다음과 같습니다.

 

[ 게시물 ]

 

- 게시물 번호

- 게시물 작성자

- 게시물 비밀번호

- 게시물 내용

- 게시물 작성일자

 

  소스코드는 다음과 같습니다.

"use strict";
const mongoose = require('mongoose');
const MONGODB_URI = process.env.MONGODB_URI;

/* Board 오브젝트를 정의합니다. */
const boardSchema = mongoose.Schema({
    id: {
        type: Number,
        required: true
    },
    name: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    },
    content: {
        type: String,
        required: true
    },
    date: {
        type: Date,
        required: true
    }
});

/* 하나의 연결 객체를 반복적으로 사용합니다. */
let connection = null;

const connect = () => {
  if (connection && mongoose.connection.readyState === 1) return Promise.resolve(connection);
  return mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true }).then(
    conn => {
      connection = conn;
      return connection;
    }
  );
};

module.exports.handler = (event, context, callback) => {
  let operation = event.httpMethod;
  let Board = mongoose.model('board', boardSchema);
  let proxy, password;
  switch (operation) {
    case 'GET':
      /* 
        경로: /board
        설명: 전체 게시글 정보를 불러옵니다.
      */
      if(event.pathParameters === null) {
        let query = {};
        if(event.queryStringParameters.name) {
          query.name = {$regex:event.queryStringParameters.name, $options: 'i'};
        }
        if(event.queryStringParameters.content) {
          query.content = {$regex:event.queryStringParameters.content, $option: 'i'};
        }
        // name과 content를 이용하여 검색한 결과를 내림차순으로 반환합니다.
        connect().then(() =>
        Board.find(query)
        .select("-password")
        .sort({id: -1})
        .exec(function(error, boards) {
            if(error) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify(error) });
            }
            else {
              context.done(null, { 'statusCode': 200, 'body': JSON.stringify(boards) });
            }
        }));
      }
      /* 
        경로: /board/:id
        설명: 특정 게시글 정보를 불러옵니다.
      */
      else {
        proxy = event.pathParameters.proxy;
        connect().then(() =>
        Board.findOne({id:proxy})
        .select("-password")
        .exec(function(err, board) {
            if(err) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify(err) });
            }
            else if(!board) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify("Board not found.") });
            }
            else {
              context.done(null, { 'statusCode': 200, 'body': JSON.stringify(board) });
            }
        }));
      }
      break;
    case 'POST':
      /* 
        경로: /board
        파라미터: {"name":"작성자","content":"내용","password":"비밀번호"}
        설명: 특정 게시글을 작성합니다.
      */
      let lastId = 0;
      // 가장 최근에 작성된 게시물 번호를 가져옵니다.
      connect().then(() =>
      Board.findOne({})
        .sort({id: -1})
        .exec(function(err, board) {
          if(err) {
            context.done(null, { 'statusCode': 500, 'body': err });
          } else {
            lastId = board ? board.id : 0;
            const { name, content, password } = JSON.parse(event.body);
            const newBoard = new Board({ name, content, password });
            newBoard.date = new Date();
            newBoard.id = lastId + 1;
            // 새로운 글을 등록합니다.
            newBoard.save(function(err, board) {
              if(err) {
                context.done(null, { 'statusCode': 500, 'body': JSON.stringify(err) });
              } else {
                context.done(null, { 'statusCode': 200, 'body': JSON.stringify(lastId + 1) });
              }
            });
          }
        })
      );
      break;
    case 'PUT':
      /* 
        경로: /board/:id
        헤더: password:"현재 비밀번호"
        파라미터: {"name":"작성자","content":"내용","password":"비밀번호"}
        설명: 특정 게시글을 수정합니다.
      */
      proxy = event.pathParameters.proxy;
      password = event.headers.password;
      // 사용자가 입력한 번호의 게시물을 찾습니다.
      connect().then(() =>
      Board.findOne({id:proxy})
      .exec(function(err, board) {
          if(err) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify(err) });
          }
          else if(!board) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify("Board not found.") });
          }
          else {
              if(board.password != password) {
                context.done(null, { 'statusCode': 500, 'body': JSON.stringify("Password is incorrect.") });
              } else {
                  const { name, content, password } = JSON.parse(event.body);
                  // 사용자가 입력한 name, content, password에 맞게 정보를 변경합니다.
                  Board.findOneAndUpdate({id:proxy}, { name, content, password })
                  .exec(function(err, board) {
                      if(err) {
                        context.done(null, { 'statusCode': 500, 'body': JSON.stringify(err) });
                      }
                      else {
                        context.done(null, { 'statusCode': 200, 'body': JSON.stringify('success') });
                      }
                  });
              }
          }
      }));
      break;
    case 'DELETE':
      /* 
        경로: /board/:id
        헤더: password:"현재 비밀번호"
        설명: 특정 게시글을 삭제합니다.
      */
      proxy = event.pathParameters.proxy;
      password = event.headers.password;
      connect().then(() =>
      Board.findOne({id:proxy})
      .exec(function(err, board) {
          if(err) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify(err) });
          }
          else if(!board) {
              context.done(null, { 'statusCode': 500, 'body': JSON.stringify("Board not found.") });
          }
          else {
              if(board.password != password) {
                context.done(null, { 'statusCode': 500, 'body': JSON.stringify("Password is incorrect.") });
              } else {
                // 사용자가 입력한 번호에 해당하는 게시물을 삭제합니다.
                Board.findOneAndRemove({id:proxy})
                .exec(function(err, board) {
                    if(err) {
                      context.done(null, { 'statusCode': 500, 'body': JSON.stringify(err) });
                    }
                    else {
                        context.done(null, { 'statusCode': 200, 'body': JSON.stringify('success') });
                    }
                });
              }
          }
      }));
      break;
    default:
      callback(new Error(`Unrecognized operation "${operation}"`));
  }
};

 

  테스트 결과는 다음과 같습니다.

 

1. 게시글 리스트 보기


경로: /board 
설명: 전체 게시글 정보를 불러옵니다. 

 

 

 

2. 게시물 정보 확인하기


경로: /board/:id 

설명: 특정 게시글 정보를 불러옵니다. 

 

 

3. 게시물 등록하기


경로: /board 
파라미터: {"name":"작성자","content":"내용","password":"비밀번호"} 
설명: 특정 게시글을 작성합니다. 

 

 

4. 게시물 수정하기

 

경로: /board/:id
헤더: password:"현재 비밀번호" 
파라미터: {"name":"작성자","content":"내용","password":"비밀번호"} 
설명: 특정 게시글을 수정합니다. 

 

 

5. 게시물 삭제하기


경로: /board/:id 
헤더: password:"현재 비밀번호" 
설명: 특정 게시글을 삭제합니다. 

 

728x90
반응형

Comment +2

728x90
반응형

  우리는 일반적으로 파이썬(Python)의 다양한 라이브러리를 이용하여 프로그램을 작성합니다. 하지만 AWS Lambda는 파이썬의 기본 라이브러리만 제공합니다. 머신러닝(Machine Learning)이나 크롤링 등을 이용하기 위해서는 추가적인 라이브러리를 사용해야 하므로 별도의 작업이 필요합니다.

 

  AWS Lambda는 라이브러리 파일을 통째로 압축해서 올려서 사용할 수 있도록 제공하고 있습니다. 따라서 한 번 크롤링을 위한 bs4 라이브러리를 이용한 파이썬 예제가 있다고 가정해 보도록 하겠습니다.

 

  아래의 소스코드를 구동시키면 bs4 라이브러리를 이용하여 구글(Google) 메인 페이지에 존재하는 모든 <a> 태그의 텍스트 부분을 크롤링하여 출력하게 됩니다.

 

import urllib.request
from bs4 import BeautifulSoup

url = "https://www.google.com"
soup = BeautifulSoup(urllib.request.urlopen(url).read(), "html.parser")
a_tags = soup.find_all("a")
result_list = []
for i in a_tags:
    result_list.append(i.get_text())

print(result_list)

 

  이러한 동작을 하는 API를 AWS Lambda를 이용하여 구현하고 싶다면 어떻게 하면 될까요? 방법은 매우 간단합니다. 먼저 bs4 라이브러리가 사용되므로 이 라이브러리를 패키지 형태로 압축해야 합니다.

 

 

  한 번 위와 같이 하나의 폴더를 만든 뒤에 명령 프롬프트(CMD)를 이용하여 해당 경로에 들어가 보겠습니다. 이후에 bs4를 설치하되 현재 폴더 위치에 설치하도록 -t 옵션을 붙입니다.

 

  pip3 install bs4 -t .

 

 

  이제 현재 폴더의 위치에 있는 모든 파일을 압축하여 bs4.zip 라는 압축 파일을 만듭니다.

 

 

  그러면 bs4.zip 파일이 생성되어 있는 것을 확인할 수 있습니다.

 

 

 

  이제 우리의 AWS Lambda 프로젝트를 확인합니다.

 

 

  이제 [.zip 파일 업로드] 유형을 선택한 뒤에 우리의 bs4.zip 파일을 업로드하면 됩니다.

 

 

  우리가 업로드 한 파일은 말 그대로 bs4 라이브러리를 통째로 압축한 파일이므로 lambda_function.py 파일이 존재하지 않는다고 출력되는 것을 확인할 수 있습니다. 왜냐하면 AWS Lambda의 Python 프로젝트의 기본 핸들러는 lambda_function 파일의 lambda_handler로 설정되어 있기 때문입니다.

 

 

  따라서 [File] 탭을 열어서 하나의 파이썬 파일을 작성하면 됩니다. lambda_function.py라는 이름으로 짓습니다.

 

import json
import urllib.request
from bs4 import BeautifulSoup

def lambda_handler(event, context):
    url = "https://www.google.com"
    soup = BeautifulSoup(urllib.request.urlopen(url).read(), "html.parser")
    a_tags = soup.find_all("a")
    result_list = []
    for i in a_tags:
        result_list.append(i.get_text())
    return {
        'statusCode': 200,
        'body': json.dumps(result_list)
    }

 

  이제 위와 같이 소스코드를 작성합니다. 기본적인 핸들러 함수는 lambda_handler이므로 함수의 이름을 이와 같이 지어줍니다. 매개변수 또한 차례대로 event, context를 넣어주세요. 람다 함수는 어떠한 결과 데이터를 return하는 방식으로 작성해주시면 됩니다.

 

 

  결과적으로 이렇게 만들어진 람다 함수를 테스트하기 위해 [저장] 버튼을 누르시면 됩니다.

 

 

  테스트 결과 실제로 크롤링 결과가 잘 출력되는 것을 확인할 수 있습니다.

728x90
반응형

Comment +2

  • lee 2020.03.03 15:00

    진짜 감사합니다. 대부분 알려준답시고 실력자 기준으로 이 정도면 알겠지? 하고 어렵게 적어논 글들이 많아서 해맸는데 이렇게
    초보자 입장에서 적어주셔서 덕분에 2주일 동안 끙끙 했던걸 해결했습니다. 감사합니다!

  • asdf 2021.07.02 01:36

    관리자의 승인을 기다리고 있는 댓글입니다

728x90
반응형

  AWS 람다를 이용하면 아마존 웹 서비스가 제공하는 별도의 플랫폼에서 손쉽게 서버를 구축해서 데이터를 저장하고, 처리하고, 출력할 수 있다는 장점이 있습니다. 우리가 구현하게 될 AWS 람다 서버를 확인해보도록 하겠습니다.

 

  구성도: 플레이어(클라이언트) -> |API 게이트웨이| -> |AWS 람다| <-> DB

 

  대략 위와 같은 구성을 가지게 됩니다. 클라이언트가 우리의 서버에 접속하면, 먼저 API 게이트웨이를 거치게 됩니다. 이후에 AWS 람다가 실제로 클라이언트의 요청(Request)을 처리하고 그 결과를 반환한다는 특징이 있습니다.

 

  따라서 람다를 만든 이후에 외부에서 해당 람다에 접근할 수 있도록 하기 위해서는 API 게이트웨이라는 것을 달아줘야 해요. AWS 람다 자체는 '함수'로서의 역할을 수행하기 때문에, 이를 실제로 서버처럼 동작하도록 하기 위해서는 API 게이트웨이를 이용해 공인 주소를 설정해야 합니다. 따라서 이러한 서버 구성도에 맞게 실제로 AWS 서비스를 이용해보도록 하겠습니다.

 

  AWS 웹 사이트https://aws.amazon.com/ko/

 

클라우드 서비스 | 클라우드 컴퓨팅 솔루션| Amazon Web Services

엔터프라이즈 변혁 AWS를 사용하여 뱅킹을 재창조하는 Capital One Capital One이 어떻게 교육, 장기 계획, 고객 요구에 대한 끊임없는 집중을 통해 클라우드 우선 전략을 구현했는지 알아보십시오. 자세히 알아보기  고객에 집중 2014년에 Capital One은 이제 막 프라이빗 클라우드 기능을 사용하기 시작하면서 AWS도 실험해 보는 단계였습니다. 이 회사는 고객이 원하는 기능을 최대한 빨리 구축하기 위해 AWS를 선택했습니다. 어려운 문

aws.amazon.com

 

  일단 가장 먼저 AWS 서비스에 로그인 해보도록 하겠습니다. AWS에 로그인을 한 이후에는 [콘솔에 로그인] 버튼을 눌러서 실제로 관리자 콘솔에 들어가주시면 됩니다.

 

  AWS 람다는 이용한 만큼만 과금을 수행하지만, 사용량이 적을 때는 사실상 거의 무료라고 보시면 돼요. 무엇보다 AWS를 처음 사용하는 사람에게는 12개월 프리 티어(Free Tier)가 제공되기 때문에 이를 이용해서 매우 효과적으로 공부해 볼 수 있습니다.

 

 

  이제 위와 같이 ‘서비스 검색’ 창에서 ‘AWS Lambda’를 검색해보도록 합시다. AWS는 정말 많은 서비스를 지원하고 있어요. AWS Lambda 말고도, 정말 많은 서비스가 있지만 사람들이 많이 사용하는 서비스는 몇몇으로 정해져 있어요. 그 중에서 람다가 많이 사용되고 있는 것이랍니다.

 

  바로 한 번 람다를 사용해보면서 익혀보도록 합시다. 람다는 특정한 서버 기능을 AWS 서비스 상에서 구현할 수 있도록 해줘요. 우리가 별도로 서버 컴퓨터를 가지고 있지 않아도, 서버 기능을 AWS를 통해 사용할 수 있는 거예요.

 

 

  이제 [함수 생성] 버튼을 눌러보도록 하겠습니다.

 

 

  이제 람다 함수의 이름을 Hello_Lambda로 설정하고, 런타임 언어를 Python 3.7을 설정합니다. 보시면, 프로그래밍 언어를 C#으로 설정할 수도 있는데요, 여기에서 말하는 C#은 .Net 프레임워크라고 하여 기본적인 C#에 비해서 난이도가 높아요. 설정해야 되는 것도 많아요. 그래서 일반적으로는 AWS 람다 함수를 만들 때에는 node.js 혹은 python이라는 언어를 많이 선택합니다. 여기서 node.js는 쉽게 말하면, 자바스크립트라고 보시면 되는데요. 이것도 웹 개발 언어에 가까우므로 우리는 python을 선택해서, 가장 쉬운 방향으로 진행하도록 할게요.

 

 

  또한 권한 설정은 기본 설정 그대로 [함수 생성]을 진행하시면 됩니다.

 

 

  람다 함수가 만들어진 이후에는, ‘트리거’ 부터 시작해서 다양한 처음보는 내용들이 나와요. 당황하지 마시고, 하나씩 살펴보도록 하겠습니다. 일단 ‘트리거’라는 것은, 람다 함수를 언제 실행할 지에 대한 내용을 의미해요. 일단 내버려 두고, 아래쪽을 살펴보시면 소스코드가 보입니다. Python을 선택하면, 이와 같이 웹 사이트 상에서 바로 소스코드를 수정해서 동작하도록 할 수 있어요.

 

  보시면 lambda_handler 함수가 처음부터 작성되어 있습니다. 이것은 어떠한 이벤트를 처리하는 함수에요. 이벤트(Event)라는 것은, 플레이어가 게임을 마치고, 게임 점수를 서버로 보내주는 등의 활동. 즉 클라이언트의 활동을 의미한다고 보시면 됩니다. 그렇다면 이벤트를 언제 발생시킬 수 있을까요?

 

  그것을 앞서 언급했던 '트리거'라는 것을 이용해서 발생시킬 수 있는 거예요. 결과적으로 이벤트가 발생하면 lambda_handler 함수의 ‘Event’ 매개변수로 값이 들어오게 됩니다. 지금은 잘 이해가 안 갈 수 있는데요. AWS와 같은 클라우드 서비스는 일단, 동작이 되도록 만든 이후에 천천히 원리를 이해하는 것이 더 학습이 빠르답니다. 따라서 일단 저와 함께 소스코드를 작성해보도록 합시다.

 

import json

def lambda_handler(event, context):
    # 이벤트가 발생하면 무조건 ‘Hello Lambda’라는 메시지를 반환합니다.
    return {
        'statusCode': 200,
        'body': json.dumps('Hello Lambda!')
    }

 

  이제 ‘저장’ 버튼을 눌러주세요. 그럼 이제 실제로 트리거(Trigger)를 설정해서 클라이언트의 요청(Request) 데이터가 AWS 웹 서버로 전송되도록 해보겠습니다. 이 때는 API 게이트웨이라는 것을 사용할 수 있어요. 일종의 웹 서버 처럼 동작하는 기능이에요. API 게이트웨이를 눌러서 트리거로 두시면, 아래쪽에 트리거 설정이 가능해요.

 

 

  가장 먼저 '새 API 생성'으로 해줄게요. 이후에 '보안'은 '열기'로 설정해줍니다. 그리고 '추가 세팅'을 눌러서 API 이름을 'Hello_Lambda-API'라고 설정하고, ‘추가’를 해서 ‘저장’을 진행하시면 됩니다. 저장이 완료되면 비로소 API 게이트웨이가 동작하는 상태가 됩니다.

 

 

  이제 우리의 Hello_Lambda-API의 '세부 정보'를 눌러보시면, API 엔드 포인트가 보입니다. 이 주소가 바로 우리의 웹 서버 경로라고 보시면 돼요. 저는 URL 주소가 https://ron0lld2le.execute-api.ap-northeast-2.amazonaws.com/default/Hello_Lambda 라고 나오네요. 이에 접속해보니 성공적으로 "Hello Lambda!"라는 문자열이 반환된 것을 알 수 있습니다.

 

 

  이로써 간단히 AWS Lambda를 이용해 우리만의 웹 서버를 순식간에 만든 거예요! 한 번 소스코드에서 'Hello Lambda!'를 'Hello!'라고 바꾸어보고, 저장하겠습니다.

 

 

그러면 다시 경로에 접속했을 때 성공적으로 바뀐 메시지가 출력된 것까지 확인할 수 있습니다.

 

 

  이러한 클라이언트와 통신하는 서버 모듈은 일반적으로 Java, C++, Python 등의 언어를 이용해 개발합니다. 과거 게임은 주로 Java와 C++을 이용해 서버 모듈을 개발하곤 했는데, 최근에는 파이썬 또한 서버 개발 언어로 정말 많이 사용하고 있습니다. 그래서 AWS Lambda가 그러한 트렌드에 맞게 Python을 적절히 지원한다는 점에서, 이를 선택한 것입니다.

728x90
반응형

Comment +0