[[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>)