...
- AWS 관리 콘솔에서 Lambda 서비스로 이동합니다.
- Create a function 버튼을 클릭하여 Lambda 함수를 하나 만들겠습니다. 또는 좌측 메뉴를 열고, Dashboard나 Functions 메뉴를 선택하고, 우측의 Create function 버튼을 클릭합니다.
- 새로운 함수의 Name은 PostNews 라고 만듭니다. Runtime은 Python 3.8 을 선택합니다. Role은 Choose an existing role 을 선택하고, 위에서 생성한 IAM Role인 NewsRole 을 선택합니다. 그리고 우측 하단의 Create function 버튼을 클릭합니다.
Lambda 함수가 정상적으로 만들어진 것을 확인할 수 있습니다. Lambda의 실행 구조는 Function Overview 메뉴 에서 볼 수 있습니다. 트리거 되면, PostNews가 실행되고, 코드 안에서 특정 서비스로 접근하기 위한 권한을 Role을 통해서 할당 받은 것을 알 수 있습니다.
Function Overview 탭을 클릭해서 접고, 아래 Code를 보겠습니다. Lambda 함수 코드 편집기는 AWS Cloud9 IDE가 내장되어 있어서 코드 편집을 웹 브라우저에서도 쉽게 할 수 있는 환경을 제공합니다. 좌측 하단의 lambda_function.py 파일을 클릭하면 기본 파이썬 소스 코드가 나오는걸 볼 수 있습니다. Runtime으로 Python 3.8이 선택되어져 있는 것을 볼 수 있습니다. Handler는 함수가 시작하기 위한 파일의 위치와 함수의 이름을 의미합니다.
아래 코드를 이 Lambda 함수의 코드로 변경합니다. 해당 함수의 로직은 Polly에 TTS 작업을 요청하고, 요청 작업에 대한 ID 값을 DynamoDB에 등록합니다.
코드 블럭 # -*- coding: utf-8 -*- from __future__ import print_function import boto3 import os import json import uuid import datetime import re def lambda_handler(event, context): if "body" in event: parmas = json.loads(event['body']) voice = parmas["voice"] originText = parmas["text"] timbre = parmas["timbre"] pitch = parmas["pitch"] updateDate = datetime.datetime.now().strftime("%Y%m%d") polly = boto3.client('polly') removeBrackets = re.sub(r'\([^)]*\)', '', originText) repTextBlock = re.sub('[·…]', '<break time="100ms"/>', removeBrackets) ssmlBlock = "<speak><amazon:effect vocal-tract-length=\"" + timbre + "\"><prosody pitch=\"" + pitch + "\">" + repTextBlock + "</prosody></amazon:effect></speak>" ssmlBlock = ssmlBlock.replace('현수', '<amazon:effect vocal-tract-length="+80%"><prosody pitch="-70%">안녕하세요? 저는 서연 친구 현수에요.</prosody></amazon:effect>') ssmlBlock = ssmlBlock.replace('귀신', '<amazon:effect name="whispered"><amazon:effect vocal-tract-length="-30%"><prosody volume="loud">나 꿍꼬또, 기싱꿍꼬또</prosody></amazon:effect></amazon:effect>') print (ssmlBlock) response = polly.start_speech_synthesis_task( OutputFormat= 'mp3', OutputS3BucketName= os.environ['BUCKET_NAME'], # OutputS3KeyPrefix='polly/', SampleRate='22050', SnsTopicArn=os.environ['SNS_TOPIC'], Text=ssmlBlock, TextType='ssml', VoiceId=voice ) print (response) data = response['SynthesisTask'] # Creating new record in DynamoDB table dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) table.put_item( Item = { 'id' : data['TaskId'], 'updateDate': data['CreationTime'].strftime("%Y-%m-%d %H:%M:%S"), 'voice' : voice, 'originText': originText, 'pollyStatus' : data['TaskStatus'], 'timbre': timbre, 'pitch': pitch, 'mp3Url': data['OutputUri'], 'RequestCharacters': data['RequestCharacters'] } ) result = { 'statusCode': 200, 'body': json.dumps({'recordId': data['TaskId']}), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return result
- 아래와 같이 함수 코드를 변경하고 나면 반드시 상단의 Deploy 버튼을 눌러서 환경을 저장합니다. 저장 되면 저장된 정보가 배포가 된 것을 확인 할 수 있습니다.
- PostNews Lambda 함수에는 코드에 외부의 환경 변수 값을 넣을 수 있는 방법을 제공합니다. 메타 데이터가 저장될 DynamoDB 테이블( DB_TABLE_NAME ), Polly가 작업을 완료하면 다음 Lambda 함수를 트리거 시키기 위한 SNS Topic( SNS_TOPIC ), Polly에 의해서 만들어지는 mp3가 저장될 S3의 버켓( BUCKET_NAME )의 이름을 지정해야 합니다. 이 값을 환경 변수로 함수에 전달할 수 있는 기능을 사용하기 위하여 Environment variables를 사용할 수 있습니다. 아래와 같이 Configuration 탭으로 이동하고, Environment varialbes를 선택한 후, Edit 버튼을 클릭하면 해당 메뉴로 진입합니다. S3 버킷 이름(
polly-mp3.studydev.com)과, NewsTopic 의 ARN 값( , DynamoDB 테이블의 NewsTable 값을 아래와 같이 입력합니다. - Tags 탭에서는 Key 값으로 Name 을 Value 값을 NewsApp 을 입력하고, 우측 상단의 Save 버튼을 클릭하여 환경을 저장합니다.
- 추가로 하단의 General configuration 에서 Lambda의 실행 시간을 지정할 수 있습니다. Timeout 값을 1 min 으로 수정하고 Save 합니다.
...
- PostNews와 같이 Create function 버튼을 클릭해서 새로운 함수를 생성합니다.
- GetNews 함수를 생성합니다. Runtime과 Role 설정은 기존과 동일합니다.
GetNews 의 함수 코드를 아래 코드로 대체합니다. 반드시 Deploy 버튼을 눌러서 저장하세요.
코드 블럭 from __future__ import print_function import boto3 import os import json import decimal from boto3.dynamodb.conditions import Key, Attr # https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/GettingStarted.Python.04.html # Helper class to convert a DynamoDB item to JSON. class DecimalEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, decimal.Decimal): if o % 1 > 0: return float(o) else: return int(o) return super(DecimalEncoder, self).default(o) def lambda_handler(event, context): if "queryStringParameters" in event: parmas = event['queryStringParameters'] print (parmas) postId = parmas["postId"] dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) if postId == "*": items = table.scan() else: items = table.query(KeyConditionExpression=Key('id').eq(postId)) response = { 'statusCode': 200, 'body': json.dumps(items["Items"], cls=DecimalEncoder), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response
- Environment variables는 key로 DB_TABLE_NAME 에 value로 NewsTable 를 입력하고, Tags에 key에 Name과 value에 NewsApp 을 입력하고, Timeout을 1 min 으로 수정한 후, 상단 우측의 Save 버튼을 클릭합니다.
...
PostNews와 같이 Create function 버튼을 클릭해서 UpdateNews 함수를 생성합니다. Runtime과 Role 설정은 기존과 동일합니다.
UpdateNews 의 함수 코드를 아래 코드로 대체합니다. 반드시 Deploy 버튼을 눌러서 저장하세요.
코드 블럭 from __future__ import print_function import boto3 import os import json from contextlib import closing from boto3.dynamodb.conditions import Key, Attr import re def lambda_handler(event, context): polly_message = event["Records"][0]["Sns"]["Message"] print (polly_message) polly_response = json.loads(polly_message) # Updating the item in DynamoDB dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) response = table.update_item( Key={ 'id': polly_response["taskId"] }, UpdateExpression="set pollyStatus = :s", ExpressionAttributeValues={ ':s': polly_response['taskStatus'] } ) print(response) s3 = boto3.client('s3') s3.put_object_acl( ACL = 'public-read', Bucket = os.environ['BUCKET_NAME'], Key = polly_response["taskId"] + ".mp3" ) return
Environment variables는 DB_TABLE_NAME에 NewsTable 값을 입력하고, BUCKET_NAME에는 S3 버킷 이름(
polly-mp3.awsdemo.kr)을 지정합니다. Tags에 Name과 NewsApp 을 입력하고, Timeout을 1 min 으로 수정한후, 우측 상단의 Save 버튼을 클릭하여 저장합니다.- UpdateNews Lambda 함수는 SNS Topic에 의해서 트리거 되어 동작이 되어야 합니다. 따라서 Function Overview에서 좌측 하단의 Add trigger 버튼을 클릭합니다.
- 트리거를 위하여 SNS를 클릭합니다. SNS topic 으로 NewsTopic 을 지정합니다. 해당 SNS에서 맞는지 확인하고 Add 버튼을 클릭하여 트리거를 등록하고 확인합니다.
...
- DeleteNews 함수를 하나 더 추가하기 위해서 Lambda 서비스의 Function List가 나오는 화면 우측 상단의 Create function 버튼을 클릭합니다.
- DeleteNews 함수를 생성합니다. Runtime과 Role 설정은 기존과 동일합니다.
DeleteNews 의 함수 코드를 아래 코드로 대체합니다. 반드시 Deploy 버튼을 눌러서 저장하세요.
코드 블럭 from __future__ import print_function import boto3 import os import json from boto3.dynamodb.conditions import Key, Attr def lambda_handler(event, context): if "body" in event: params = json.loads(event['body']) print (params) # Bad Request if params["postId"] is None or params["postId"] == "": response = { 'statusCode': 400, 'body': json.dumps({'message': "An unknown error has occurred. Missing required parameters."}), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response postId = params["postId"] dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) table.delete_item(Key={"id":postId}) s3 = boto3.client('s3') s3.delete_object(Bucket=os.environ['BUCKET_NAME'], Key= postId + ".mp3") response = { 'statusCode': 200, 'body': json.dumps({'message': "item is deleted : " + postId}), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response
Environment variables는 DB_TABLE_NAME에 NewsTable 값을 입력하고, BUCKET_NAME에는 S3 버킷 이름(
polly-mp3.awsdemo.kr)을 지정합니다. Tags에 Name과 NewsApp 을 입력하고, Timeout을 1 min 으로 수정합니다.- 아래와 같이 4개의 람다 함수가 생성된 것을 확인할 수 있습니다.
10. Lambda 함수를 RESTful 웹 서비스로 만들기
마지막으로해야 할 일은 애플리케이션 로직을 RESTful 웹 서비스로 노출시켜 표준 HTTP 프로토콜을 사용하여 쉽게 호출 할 수 있도록 합니다. 이를 위해 Amazon API Gateway를 사용합니다.
- API Gateway 서비스로 이동합니다.
- API Gateway 콘솔에서 시작 버튼을 눌러서 REST API를 생성을 시작합니다.
- 다음과 같이 생성 화면이 나오는 것을 확인할 수 있습니다.
- REST 방식으로 New API 를 선택고 API 이름은 newsapi 로 설정하고, Create API 버튼을 클릭합니다. Endpoint Type은 기본 설정과 같이 Regional 을 선택합니다.
- API가 생성 된 후, 우리는 3 개의 HTTP 메소드( Actions 버튼을 클릭 후 메서드 생성)를 생성하고 CORS 설정을 합니다.
- / 패스를 선택한 상태에서, Actions 버튼을 클릭하여 Create Method 를 클릭합니다.
- Method 타입을 선택할 수 있습니다.
- GET 을 선택합니다. 그리고 다른 메서드를 추가하기 위해서 다시 / 패스를 선택하고 Actions 버튼을 클릭하여 Create Method 를 클릭 합니다.
- 같은 방식으로 POST 와 DELETE 도 추가 합니다.
- / 패스를 선택한 상태에서, Actions 버튼을 클릭하여 Create Method 를 클릭합니다.
- API Gateway에 대한 각 Method 요청에 대해서 Lambda 함수를 연결합니다.
- GET Method로 요청이 올 경우, GetNews Lambda 함수를 호출하도록 설정합니다. Use Lambda Proxy Integration 체크 박스 를 클릭합니다. 우측 하단의 Save 버튼을 클릭하면 저장합니다.
- API Gateway가 GetNews Lambda 함수를 호출(Invoke) 할 수 있도록 권한을 추가 하냐고 물으면 OK 버튼을 클릭합니다.
- POST Method 역시 동일한 방법으로 PostNews Lambda 함수를 호출하도록 지정합니다.
- PostNews 로 접근할 수 있는 권한을 부여합니다.
- DELETE Method 역시 동일한 방법으로 DeleteNews Lambda 함수를 호출하도록 지정합니다.
- 모던 브라우저의 경우 CORS 이슈가 발생할 수 있습니다. 브라우저가 정적 웹 호스팅 중인 S3 버켓에 접속한 상태에서 API Gateway로 배포한 URL로 동적 컨텐츠 호출을 하면 서로 다른 도메인 문제가 발생할 수 있습니다. 이를 해결하기 위해서 / 패스에서 Actions 버튼을 클릭하고 Enable CORS를 클릭합니다.
- 모든 설정 값을 기본 설정 그대로 두고, 우측 하단의 Enable CORS and replace existing CORS headers 버튼을 클릭합니다.
- 다음과 같이 팝업창이 나오면서 적용을 할 것인지 물어 봅니다. Yes, replace existing values 버튼을 클릭합니다.
- 아래와 같이 CORS 설정이 정상적으로 적용되는 것을 확인 할 수 있습니다.
API 설정이 완료 되었습니다. 이제 배포를 해서 애플리케이션에서 호출할 수 있는 URL을 얻습니다. Actions 에서 Deploy API 를 선택합니다.
이제 API를 state 스테이지로 배포를 합니다. 개발, 테스트, 프로덕션에 이르기까지 다양한 스테이지로 나누어서 배포가 가능합니다. 여기서는 stage 로 넣고 Deploy 합니다.
이제 API 배포까지 완료되었습니다. 해당 API를 호출 할 수 있는 URL이 생성되었음을 확인할 수 있습니다.
정보 아래와 같이 Invoke URL을 미리 메모장에 기록해 둡니다. 해당 URL을 동적 컨텐츠 수집을 위한 URL로 활용할 예정입니다. (본인의 계정에서 생성된 URL을 이용하세요.)
https://7xxxxxxxxxi6.execute-api.ap-northeast-2.amazonaws.com/stage
- API Gateway 리소스 관리를 위한 태깅 작업으로 Configure Tags 버튼을 클릭합니다. Tag Editor가 화면에 출력되면 Name과 NewsApp을 각각 넣어 주고 저장합니다.
- GET Method로 요청이 올 경우, GetNews Lambda 함수를 호출하도록 설정합니다. Use Lambda Proxy Integration 체크 박스 를 클릭합니다. 우측 하단의 Save 버튼을 클릭하면 저장합니다.
...