[[AWSメモ]] >
* AWS API Gateway&LambdaをFlaskに移行しやすいように作る [#ibb8bd56]
#setlinebreak(on);

#contents
-- 関連
--- [[ChaliceでAPI Gateway&Lambda開発]]
--- [[Nginx+uwsgi+Flaskをdockerで動かす]]
-- 参考
--- [[https://github.com/aws/chalice]]

** 概要 [#nca4a038]
#html(<div style="padding-left: 10px;">)

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

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


#html(</div>)

** メインハンドラ [#g6afcfa1]
#html(<div style="padding-left: 10px;">)

app.py
#mycode2(){{
# 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 だけ)
#mycode2(){{
# (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'}
}}
#html(</div>)

#html(</div>)

** ラッパー [#qdc0ed93]
#html(<div style="padding-left: 10px;">)

myframework.py
#mycode2(){{
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?
}}
#html(</div>)

** template.yml [#t81396c9]
#html(<div style="padding-left: 10px;">)

#mycode2(){{
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
}}

#html(</div>)

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS