AWSメモ >

AWS API Gateway&LambdaをFlaskに移行しやすいように作る

概要

素の API Gateway & Lambda が、仮にサーバを立てて運用するように変わっても移行しやすい形を考えてみた。
※EC2 & flask + uwsgi + nginx に移行する場合とか。

以下、Flask に移行するケースを考えてみた。
※ というか、最初から chalice で作っていれば、殆ど手を入れる事なく移行できると思う。
ChaliceでAPI Gateway&Lambda開発 も参照

メインハンドラ

app.py

# TODO: ビジネスロジックは、Lambdaのコンテキストに依存しない形で別ファイルに外出しする

from myframework import WebApp

app = WebApp()

@app.route('/books', methods=['GET'])
def book_list(event, context):
    return {'body': f'books_list'}

@app.route('/books', methods=['POST'])
def book_create(event, context):
    return {'body': f'book_create'}

@app.route('/books/{id}', methods=['PUT'])
def book_update(event, context, id):
    return {'body': f'book_update : {id}'}

@app.route('/books/{id}', methods=['DELETE'])
def book_update(event, context, id):
    return {'body': f'book_delete : {id}'}

他のインターフェース例(サンプルなので book_update だけ)

# (1) コンテキスト依存を無くしたい場合(リクエストデータ、リクエストヘッダ)
@app.route('/books/{id}', methods=['PUT'])
def book_update(data, headers):
    return {'body': f'book_update : {data["id"]}'}

# (2) コンテキスト依存を無くしたい場合(ラッパーリクエストで受渡し)
@app.route('/books/{id}', methods=['PUT'])
def book_update(request):
    return {'body': f'book_update : {request["path_params"]["id"]}'}

# (3) flask、chalice的
@app.route('/books/{id}', methods=['PUT'])
def book_update(id):
    return {'body': f'book_update : {id}\n'}

# (4) Lambdaハンドラのインターフェースそのままにする場合
@app.route('/books/{id}', methods=['PUT'])
def book_update(event, context):
    id = event['pathParameters']['id']
    return {'body': f'book_update : {id}\n'}

ラッパー

myframework.py

import json

class WebApp(object):

    def __init__(self):
        self.path_infos = []

    def route(self, path, methods=[]):
        def d_wrapper(f):
            l_methods = [x.lower() for x in methods]
            self.path_infos.append({'f': f, 'path': path, 'methods': l_methods})
            def f_wrapper(*args, **kwargs):
                print(f'decorator path    : {path}')
                print(f'decorator methods : {l_methods}')
                print(f'function  args    : {args}')
                print(f'function  kwargs  : {kwargs}')
                f(*args, **kwargs)
            return f_wrapper
        return d_wrapper


class WebRequest(object):
    def __init__(self, event):
        self.http_method = event.get('httpMethod') if event.get('httpMethod') else ''
        self.path_params = event.get('pathParameters') if event.get('pathParameters') else {}
        self.query_params = event.get('queryStringParameters') if event.get('queryStringParameters')  else {}
        self.headers = event.get('headers') if event.get('headers') else {}

    def to_dict():
        return {
            'http_method': self.http_method,
            'path_params': self.path_params,
            'query_params': self.query_params,
            'headers': self.headers
        }

def handler(event, context):
    """全Lambdaで共通のメインハンドラ."""
    print('main handler')

    # アプリケーションをインポート 
    from app import app 

    print(event)

    # リクエスト情報の取得
    resource = event.get('resource') if event.get('resource') else '/' 
    http_method = event.get('httpMethod') if event.get('httpMethod') else ''
    path_params = event.get('pathParameters') if event.get('pathParameters') else {}
    query_params = event.get('queryStringParameters') if event.get('queryStringParameters')  else {}

    # 対象関数の判定
    target_function = None
    for info in app.path_infos:
        check_path = info['path']
        for name, val in path_params.items():
            check_path = check_path.replace('{'+name+'}', val)
        if check_path == resource and http_method.lower() in info['methods']:
            target_function = info['f']

    # リクエストデータを纏める(パスパラメータ、GETパラメータ、POSTデータを1つの辞書に纏める)
    #json_text = event.get('body') if event.get('body') else '{}'
    #json_data = json.loads(json_text)
    #request_data = {**path_params, **query_params, **json_data}
    #request_headers = event['headers']

    # 対象関数の実行
    if target_function:
        return target_function(event, context, **path_params)      # event, context, パスパラメータ
        #return target_function(request_data, request_headers)   # (1) コンテキスト依存を無くしたい場合(リクエストデータ、リクエストヘッダ)
        #return target_function(WebRequest(event))                     # (2) コンテキスト依存を無くしたい場合(ラッパーリクエストで受渡し)
        #return target_function(**path_params)                             # (3) flask、chalice的
        #return target_function(event, context)                              # (4) Lambdaハンドラのインターフェースそのままにする場合
    else:
        return {'statusCode': 404}  # 403?

template.yml

AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Test serverless application.

Resources:

  BookFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: myframework.handler
      Runtime: python3.6
      Events:
        List:
          Type: Api 
          Properties:
            Path: /books
            Method: get 
        Create:
          Type: Api 
          Properties:
            Path: /books
            Method: post
        Update:
          Type: Api 
          Properties:
            Path: /books/{id}
            Method: put 
        Delete:
          Type: Api 
          Properties:
            Path: /books/{id}
            Method: delete

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-01-10 (木) 15:35:53 (315d)