AWS Lambda에 Python Handler 만들기


이번에는 Python으로 구현해 보겠습니다.

Previously on Lambda series

Lambda 와 API Gateway 연동 #3 (Proxy Resource)까지 진행되었다는 가정하에 진행하겠습니다.
아직 위 내용들을 보지 않으셨던 분은 아래대로 따라하시면 준비가 끝납니다.

그 과정을 간략하게 살없이 뼈만 간추리면 다음과 같습니다. 관련 내용은 위 포스트 들을 참조해주세요. 그냥 읽어봐서 이해가 안되신다면 한 번 아무생각없이 따라해 보시면 이해가 되실겁니다.

Create Lambda

  • AWS 로그인 후 Lambda 탭으로 이동
  • Create a Lambda Function 선택
  • Select blueprint에서 Blank Function 선택
  • Configure function 에서 일단 바로 Next 선택 (미리 연결할 API Gateway가 있다면 여기서 연결하면 됨)
  • Configure function에서 함수 정의
    • Name : testLambda-luna
    • Runtime : Node.js 4.3
    • Code : 일단 아무거나 입력
    • Role & Existing role : 일단은 적당히 선택 (만약 Lambda에서 다른 AWS 서비스 RDS, S3 등을 연동할려면 필요)
    • 아래 코드 입력 후 Next 선택
  • Create Function 선택

Set API Gateway

  • AWS 메인 화면으로 이동 후 API Gateway 탭으로 이동
  • Create API 선택
    • API name : testAPI-luna
    • Create API 선택
  • /에서 Actions -> Create Resource를 선택
    • Configure as proxy resource 를 체크한 후 Create Resource를 누름
  • ANY 선택
    • Integration Request 선택
      • Integration type : Lambda Function
        • Lambda Region : 람다를 생성한 지역 서버 선택
        • Lambda Function : 람다 명칭 기입
  • Actions -> Deploy API 선택 후 그냥 prod로 스테이징

Hello Python Lambda Handler

먼저 가장 기초적인 핸들러를 작성해보록 하죠.

앞서 생성한 Lambda 설정에 들어가셔서

  • Configuration
    • Runtime : Python 2.7 선택
    • Handler : index.handler라고 되어 있는지 확인
  • Code 탭 으로 와서 아래 코드 입력
def handler(event, context):
    return { 'event': str(event), 'context': str(context) }

그런 다음 API Gateway의 주소를 브라우저에서 입력하면 오류가 발생합니다. 주소 확인하는 법은 API Gateway 설정에서 Stages를 눌러서 해당 스테이징(prod)를 선택하면 Invoke URL에 표시됩니다

그 이유는 현재 {proxy+} 이하로는 ANY로 설정된게 있는데 /에는 아무것도 설정된게 없기 때문입니다.

  • 좌측 해당 항목에서 Resources로 이동
    • /에서 Actions -> Create Method를 눌러서 ANY를 생성
      • 생성된 ANY를 눌러서
        • Integration type : Lambda Function
          • Lambda Region : 람다를 생성한 지역 서버 선택
          • Lambda Function : 람다 명칭 기입
          • Save를 누른 뒤 화면이 바뀌면
            • Integration Request 선택
            • Body Mapping Templates 선택
              • Add mapping template 선택
              • application/json이라고 입력한 뒤 적용
                • Generate template : Method Request passthrough 선택
                • Save 선택

API Gateway는 설정 변경 후 반드시 Deploy해줘야만 적용됩니다.

  • Actions -> Deploy API 선택 후 그냥 prod로 스테이징

이제 다시 Invoke URL로 접속하면 뭔가 화면에 결과가 나타나는 것을 볼 수 있습니다.

{"event": "{u'body-json': {}, u'params': {u'path': {}, u'querystring': {}, ...}

뭔가 이런 지저분한 모양입니다. u라고 따옴표 앞에 붙은것을 다 지우고 홀따옴표(')를 쌍따옴표(")로 수정한 뒤 JSON 모양으로 나타내 보면 아래와 같이 됩니다. 우리가 필요한 항목들만 적어봤습니다.

{
  "event": {
    "body-json": {},
    "params": {
      "path": {},
      "querystring": {},
      "header": {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
      }
    },
    "stage-variables": {},
    "context": {
      "http-method": "GET",
      "resource-path": "/",
      "source-ip": "112.217.228.202",
    }
  },
  "context": "<__main__.LambdaContext object at 0x7f629a39aa10>"
}

Node.JS와 똑같은 모양으로 나옵니다.
context는 뭔가 dict타입이 아니라서 그대로 출력이 되지 않는군요. 공식문서 (http://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html)를 보시면 context 오브젝트에 대한 자세한 내용이 나옵니다.

이제 기본 Invoke URL 뒤에 /test?id=2라고 적은 뒤 브라우저로 요청을 해보겠습니다.

당연히 오류가 발생합니다.

{proxy+}로 요청하는 데이터의 형식도 다르며 여기에 대한 응답은 JSON 오브젝트 bodyJSON 형식의 문자열로 전달해야 합니다.

Lambda쪽 코드를 아래와 같이 수정 후 다시 요청해보면 정상적으로 답변이 오는 것이 확인 됩니다.

POSTMAN을 통해서 POST로 요청을 해도 정상적으로 답변이 오는 것을 볼 수 있습니다. (JSON 객체형태가 아니기 때문에 출력 형식을 Text로 해야 보입니다.)

그래서 JSON 형식으로 응답하도록 코드를 조금 수정해 봤습니다.

import json

def handler(event, context): 
    return { 'body' : json.dumps(event) }  

이제는 JSON 오브젝트로 응답하므로 POSTMAN에서도 바로 결과를 볼 수 있습니다.

Python Routing Example

조금 더 복잡한 예제를 작성해 보겠습니다. 예제의 내용은 Node.JS로 진행한 예제와 같은 것입니다.

/test?id=? 라는 주소로 GET, POST요청에 대해서 각각 다른 작업을 하는 코드를 작성해 보겠습니다.

각각 함수에 대한 설명은 Lambda 와 API Gateway 연동 #2 (ANY, Deploy Staging, Node.JS Route) 포스팅을 참고해 주세요.

바로 코드 들어갑니다.

import json

def get(event):
    user_id = event['queryStringParameters']['id']
    return { 'body': { 'id': user_id, 'name': "test" } }

def post(event):
    user_id = event['queryStringParameters']['id']
    body = event['body']
    header = event['headers']
    return { 'body': { 'id': user_id, 'header': header, 'body': body } }

route_map = {
    '/test': {
        'GET': get,
        'POST': post
    }
};

def router(event):
    controller = route_map[event['path']][event['httpMethod']];
    
    if not controller:
        return { 'body': { 'Error': "Invalid Path" } }
    
    return controller(event);

def handler(event, context):
    result = router(event);
    return { 'body' : json.dumps(result) }

결과 확인

  • GET 요청 : https://.../prod/test?id=2
{"body":{"id":"2","name":"test"}}
  • POST 요청 : URL은 GET과 동일
{
  "body": {
    "id": "2",
    "header": {
      ...
    },
    "body": "\"{\\n  \"id\": \"123\",\\n  \"age\": \"25\"\\n}\""
  }
}

Node.JS 와 똑같은 결과로 출력되는 것이 확인 가능합니다.

마치며…

Python Packaging는 나누어서 다음 포스팅에 작성하겠습니다. 간략하게 방법을 설명드리지만 virtualenv로 작업 환경을 만든 뒤 pip install 모듈명 -t 프로젝트폴더로 작업폴더에 모듈을 설치한 뒤 .zip파일로 압축하여 올리시면 됩니다. (Node.JS Packaging과 크게 다르지 않습니다.)

이번 포스팅에서 알아본 내용들은 다음과 같습니다.

  • LambdaPython 코드로 핸들러 작성
  • Python에서 API Gateway{proxy+}요청에 대하여 구분해서 작업하는 방법

잘못되었거나, 변경된 점, 기타 추가 사항에 대한 피드백은 언제나 환영합니다. - seokjoon.yun@gmail.com

참고

다음글

AWS Lambda 관련 5번째 포스트 입니다.
지난 글들의 목록은 다음과 같습니다.


이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)