[AWS] IP 반환 서버 만들기

그냥·2022년 7월 27일
0

AWS

목록 보기
6/9

1. 기획

1) 목표

  • url에 접속했을 때 IP가 DB에 저장되고 데이터를 조회할 수 있는 서버를 만든다.

2) Method

  • POST: body에 { "random_number" : int_type } 을 던져 주면 DB에 IP주소, 데이터 생성 시간, 업데이트 시간, random_number가 저장된다.

    Request

    { "random_number" : int_type }
  • GET: DB에서 IP주소, 데이터 생성 시간, 업데이트 시간, random_number을 불러온다.


3) Flow




2. RDS & Lambda 연결

해당 부분은 아래 링크를 참고해서 진행한다.

RDS Lambda 연결하기




3. API Gateway

1) Lambda 함수 생성

  • API Gateway에 연결할 Lambda 함수를 생성한다.

2) API Gateway API 생성

API Gateway 를 참고하여 아래 작업 진행

  • API Gateway에서 REST API 생성

  • 리소스 생성

  • 리소스 밑에 GET, POST 메소드 생성 (프록시 통합 사용은 하지 않는다)

  • 메서드 요청 > 요청 본문 > 모델 추가 선택
    - 콘텐츠 유형: applcation/json 작성
    - 모델 이름: empty 선택

  • 통합 요청 > 매핑 템플릿 > 정의된 템플릿이 없는 경우(권장) 선택
    - 매핑 템플릿 추가 클릭 후
    - Content-Type에 application/json 작성 후 오른쪽 체크 클릭
    - 그러면 아래에 템플릿 생성 > 메서드 요청 패스스루 를 선택

    매핑 템플릿: 매핑 템플릿이란 Lambda 함수로 보내지는 요청 양식이다. 이후 Lambda 함수에서 event 등의 변수로 요청을 받을 때, 요청 변수 안에서 필요한 값을 추출(인덱싱) 해야 하기 때문에 추출하기 가장 편한 템플릿을 사용하는 것이 좋다.


3) CORS 설정

CORS를 설정하는 이유는 외부에서 API 호출을 가능하게 해주기 위해서이다. 예를 들어 index.html을 만들어서 fetch를 요청을 보내거나, postman으로 요청을 보낼 때도 외부에서 요청을 보내는 것이기 때문에 CORS 설정을 해주어야 502에러가 발생하지 않는다.

  • 리소스 페이지로 다시 와서 메서드를 생성한 리소스를 클릭한다.
  • 클릭 후 [작업] > [CORS 활성화]를 클릭한다.
  • [메서드]에 CORS를 활성화할 메서드를 선택한다.
  • [CORS 활성화 및 기존의 CORS 헤더 대체]를 클릭한다.
  • [작업] > [API 배포]를 클릭하여 배포를 하면 CORS가 활성화된다.



4. Lambda

1) 파일 구성

ikaria-lambda2
    |
    |----pymysql
    |----PyMySQL-1.0.2.dist-info
    |----pytz
    |----pytz-2020.4.dist-info
    |----dbinfo.py
    |----lambda_function.py
  • pymysql & PyMySQL-1.0.2.dist-info : python에서 MySQL과 연결시켜주고 CRUD를 가능하게 해주는 라이브러리
  • pytz & pytz-2020.4.dist-info : 한국시간을 가져오기 위해서 사용하는 라이브러리
  • dbinfo.py : DB 정보가 담겨있는 파일. 여기서 DB 정보를 import 해와서 사용한다.
  • lambda_function.py : 실제 요청을 처리하는 함수가 들어있는 파일

2) lambda 초기 세팅

  • [Lambda] > [코드] 에서는 파이썬 라이브러리가 하나도 없기 때문에 로컬에서 필요한 라이브러리를 설치한다.

  • 설치한 라이브러리들을 하나의 디렉토리에 모두 복사해둔다.

  • dbinfo.py과 lambda_function.py도 디렉토리에 만들어 둔다.

  • 해당 디렉토리를 압축한다.

  • Lambda 함수 대시보드에 접속한 후 오른쪽에 있는 [에서 업로드] > [zip 파일] > [압축 파일]을 클릭한다.

  • [압축 파일]을 업로드 하여 사용시 기존의 코드는 모두 없어지므로 주의한다.

  • 아래는 db_info.py 와 lambda_function.py의 형식이다.

    # db_info.py
    
    db_host = 'DB 엔드포인트'
    db_username = 'DB username'
    db_password = 'DB password'
    db_name = 'DB 이름'
    db_port = 포트
    # lambda_function.py
    
    def lambda_handler(event, context):
    	return event

3) lambda_function

import sys
import logging
import pymysql
import dbinfo
import json
from datetime import datetime
from random   import randrange
from pytz     import timezone

def lambda_handler(event, context):
    
    # 1
    connection = pymysql.connect(
      host   = dbinfo.db_host,
      user   = dbinfo.db_username,
      passwd = dbinfo.db_password,
      db     = dbinfo.db_name,
      port   = dbinfo.db_port
    )
    
    # 2
    if 'httpMethod' not in event:
    	# 2-1
        if event['context']['http-method'] == "POST":
            number        = event['body-json']['random_number']
            random_number = randrange(number)
            ip            = event['context']['source-ip']
            now           = str(datetime.now(timezone('Asia/Seoul')))[:-13]
            
            # 2-2
            cursor = connection.cursor()
            data   = (ip, now, now, random_number, now, random_number)
            sql    = """
            INSERT INTO ips (ip, created_at, updated_at, random_number) 
            VALUES (%s, %s, %s, %s) 
            ON DUPLICATE KEY UPDATE updated_at=%s, random_number=%s;
            """
            
            cursor.execute(sql, data)
            connection.commit()
            connection.close()
            
            result = {
                'result': 'success'
            }
            
            # 2-3
            return {
                'statusCode': 201,
                'headers': {
                    'Content-Type': 'application/json'
                 },
                'body': json.dumps(result),
                "isBase64Encoded": False
            }
            
    # 3
    else:
    	# 3-1
        if event['httpMethod'] == "GET":
            cursor = connection.cursor()
            cursor.execute("select * from ips;")
            rows = cursor.fetchall()
        	
            # 3-2
            results = [{
                'id' : row[0],
                'ip' : row[1],
                'created_at' : str(row[2]),
                'updated_at' : str(row[3]),
                'random_nubmer' : row[4]
            }for row in rows]
            
            connection.close()
            
            return {
                'statusCode': 200,
                'headers': {
                    'Content-Type': 'application/json',
                    'Access-Control-Allow-Origin': '*'
                 },
                'body': json.dumps(results),
                "isBase64Encoded": False
                }

# 1
    connection = pymysql.connect(
      host   = dbinfo.db_host,
      user   = dbinfo.db_username,
      passwd = dbinfo.db_password,
      db     = dbinfo.db_name,
      port   = dbinfo.db_port
    )
    
- pymysql을 사용하여 lambda와 rds를 연결해주는 작업
- 이후 connection 변수를 사용해서 DB CRUD를 진행

# 2
    if 'httpMethod' not in event:
    	...
        ...
    
# 3
    else:
    
- # 2와 # 3은 요청이 POST와 GET 일 때 따라서 다른 로직이 실행 될 수 있게 분기 처리를 해준 것이다.
- POST와 GET 요청에 대해서 서로 다른 매핑 템플릿을 사용하여서 
  GET으로 요청했을 때는 event 바로 아래에 'httpMethod'가 있어서
  event['httpMethod']를 통해서 요청 메서드를 뽑아낼 수 있었다.
  
  그러나 POST로 했을 때는 event - 'context' - 'http-method' 여기에 요청 메서드가 있었다.
  그렇기 때문에 event['httpMethod']를 할 경우 KeyError가 발생하였다.
  이를 해결하기 위해 if 'httpMethod' not in event 구문을 사용하여 분기 처리를 하였다.
  
  만약 하나의 함수에서 여러 메서드 처리를 한다면 매핑 템플릿을 통일 시켜주는 것이 좋다.

# 2-1
        if event['context']['http-method'] == "POST":
            number        = event['body-json']['random_number']
            random_number = randrange(number)
            ip            = event['context']['source-ip']
            now           = str(datetime.now(timezone('Asia/Seoul')))[:-13]
            
- if ... : 메서드가 POST인 경우 아래 로직 실행
- number = event['body-json']: 클라이언트에서 보낸 body 값을 추출
- random_number = randrange(number): number를 바탕으로 랜덤한 정수값 추출
- ip = event['context']['source-ip']: 클라이언트 요청 안에서 ip를 추출
- now = str(datetime.now(timezone('Asia/Seoul')))[:-13]:
  한국시간으로 현재 시간을 now 변수에 저장

# 2-2
            cursor = connection.cursor()
            data   = (ip, now, now, random_number, now, random_number)
            sql    = """
            INSERT INTO ips (ip, created_at, updated_at, random_number) 
            VALUES (%s, %s, %s, %s) 
            ON DUPLICATE KEY UPDATE updated_at=%s, random_number=%s;
            """
            
            cursor.execute(sql, data)
            connection.commit()
            connection.close()
            
- cursor = connection.cursor(): DB에 CRUD을 할 수 있게 됨
- data: sql 변수 안에 있는 %s에 들어갈 값들
- sql: insert into ~ 쿼리문을 사용해서 unique key인 ip에 대해서 DB에 지금 들어온 
  ip가 없으면 데이터 추가를, 있으면 ON DUPLICATE KEY UPDATE 뒤의 값으로 수정한다.
- cursor.execute(sql, data): 쿼리문을 DB에 실행
- connection.commit(): DB에 실행된 쿼리문이 commit 됨
- connection.close(): DB 연결을 끊어주어 에러가 발생하지 않게 한다.

# 2-3
        return {
            'statusCode': 201,
            'headers': {
                'Content-Type': 'application/json'
             },
            'body': json.dumps(result),
            "isBase64Encoded": False
        }
        
- REST API 형식에 맞춰서 return을 해준다.
- 위 형식에 맞지 않을 경우 502 internal server error가 발생 할 수 있으므로 위 형태에 
  맞춰서 return 해주는 것이 좋다.

# 3-1
        if event['httpMethod'] == "GET":
            cursor = connection.cursor()
            cursor.execute("select * from ips;")
            rows = cursor.fetchall()
            
- if event['httpMethod'] == "GET": 메서드가 GET일 경우 아래 로직 진행
- cursor = connection.cursor(): DB에 접근 할 수 있게 해준다.
- cursor.execute("select * from ips;"): ips에서 모든 로우를 호출한다.
- rows = cursor.fetchall(): rows에 DB에서 뽑아온 row값을 모두 담는다.

 # 3-2
            results = [{
                'id' : row[0],
                'ip' : row[1],
                'created_at' : str(row[2]),
                'updated_at' : str(row[3]),
                'random_nubmer' : row[4]
            }for row in rows]

            connection.close()
            
- results: rows에서 row 값을 하나씩 호출하여 row[]처럼 인덱싱하여 데이터를 뽑아온다.
  row가 여러개 이기 때문에 [{}, {}, ...] 처럼 형태가 만들어진다.
- connection.close(): cursor = connection.cursor()를 통해서 DB와 연결이 되어 있는 상태였다.
  연결을 끊어주어야 이후 오류가 발생하지 않는다.

0개의 댓글