LambdaでURLのルーティングを行う

# はじめに

LambdaでHTTP接続する際、URLのパスで処理を変えたいと思い試した内容を記事にします。

# 参考

https://speakerdeck.com/_kensh/web-frameworks-on-lambda

# 概要
前提として、Pythonを用いています。
以下の2つのやり方を見つけました。

- AWSGIを使う方法
    - LambdaでFlaskを使える
    - API Gateway経由のeventを処理可能
        - Function URLのeventはできない
- AWS Lambda Powertoolsを使う方法
    - ユーティリティーのライブラリ
        - 今回のイベントハンドラ以外のユーティリティも提供
        - Python以外の言語でも提供
    - API Gateway経由のeventも、Function URLのeventも可能
        - 未検証だが、ALB経由のも可能な模様


# やってみた

いずれの方法もAWS SAMを用いています。私はCloud9上で行いました。

## AWSGIを使う方法
使い方は以下のページにありますので、そちらをご参考ください。

https://ryosh.github.io/posts/20210421/

Flaskを使われていた方には、いいかもしれません。



## AWS Lambda Powertoolsを使う方法
以下のページを参考に、コードを書いてみました。

https://awslabs.github.io/aws-lambda-powertools-python/2.14.1/core/event_handler/api_gateway/#event-resolvers

https://pages.awscloud.com/rs/112-TZM-766/images/20221124_24th_ISV_DiveDeepSeminar_Lambda_Powertools.pdf

まず使用するフォルダ、ファイルを作ります。
```bash
mkdir -p sample-lambda_powertools/functions && cd sample-lambda_powertools
touch functions/app.py
touch functions/requirements.txt
touch template.yaml
```
それぞれのファイルに記載していきます。

まずはインポートするモジュールを宣言します。
```:functions/requirements.txt
aws-lambda-powertools
```
### Function URLの場合
Function URLの場合は、`LambdaFunctionUrlResolver`を用います。

```python:functions/app.py
from aws_lambda_powertools.event_handler import LambdaFunctionUrlResolver

app = LambdaFunctionUrlResolver()

@app.get("/")
def get_home():
  return "get_home"
  
@app.post("/")
def post_home():
  return "post_home"
  
@app.put("/")
def put_home():
  return "put_home"

@app.delete("/")
def delete_home():
  return "delete_home"

@app.get("/hello")
@app.post("/hello")
def hello():
  return "hello"
  
@app.get("/hello/<inputname>")
@app.post("/hello/<inputname>")
def hello_withname(inputname : str):
  return f"hello {inputname}!"

def lambda_handler(event, context):

  print('sample function')
  return app.resolve(event, context)
```

Function URLを持つLambdaを作るSAMのテンプレートは以下になります。

```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for Lambda Function

Resources:
  TargetFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/
      Handler: app.lambda_handler
      Runtime: python3.9
      
  TargetFunctionUrl:
    Type: AWS::Lambda::Url
    Properties:
      AuthType: NONE
      TargetFunctionArn: !GetAtt TargetFunction.Arn
      
  TargetFunctionUrlPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunctionUrl
      FunctionName: !GetAtt TargetFunction.Arn
      FunctionUrlAuthType: NONE
      Principal: "*"
      
  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${TargetFunction}
      RetentionInDays: 3653
      
Outputs:
  FunctionUrl:
    Value: !GetAtt TargetFunctionUrl.FunctionUrl
    Export:
      Name: TargetFunctionUrl
```

あとはBuildしてDeployです。
```bash
sam build && sam validate
sam deploy --guided
```

CURLコマンドで確かめられられます。
```bash
curl -X POST [関数URL]
curl -X POST [関数URL]/hello
curl -X POST [関数URL]/hello/taro
```


### API Gatewayの場合
API Gatewayを使う場合は、`APIGatewayRestResolver`を用います。
他の部分はFunction URLのコードと変わらないです。

```python:functions/app.py
from aws_lambda_powertools.event_handler import APIGatewayRestResolver

app = APIGatewayRestResolver()

@app.get("/")
def get_home():
  return "get_home"
  
@app.post("/")
def post_home():
  return "post_home"
  
@app.put("/")
def put_home():
  return "put_home"

@app.delete("/")
def delete_home():
  return "delete_home"

@app.get("/hello")
@app.post("/hello")
def hello():
  return "hello"
  
@app.get("/hello/<inputname>")
@app.post("/hello/<inputname>")
def hello_withname(inputname : str):
  return f"hello {inputname}!"

def lambda_handler(event, context):

  print('sample function')
  return app.resolve(event, context)
```

SAMテンプレートは以下になります。
```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for Lambda Function

Resources:
  TargetFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/
      Handler: app.lambda_handler
      Runtime: python3.9
      Events:
        ApiRoot:
          Type: Api
          Properties:
            Path: '/'
            Method: ANY
        ApiProxy:
          Type: Api
          Properties:
            Path: '/{proxy+}'
            Method: ANY
      
  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${TargetFunction}
      RetentionInDays: 3653

Outputs:
  TargetdApi:
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
```

あとはBuildとDeployで作成し、CURLコマンドで確かめられます。

# おわりに

Flaskを勉強していた流れで知った知識であり、色々使いどころがありそうです。
ただ、1つの関数をリッチにしがちになりそうなので、そこは注意が必要かと感じます。