Amazon API Gateway では、 Open API 仕様(OpenAPI-Specification)をインポートすることで REST API を作成できます。
例えば、/order のパスで POST リクエストを受け付ける API を Open API 仕様に基づきファイルに記述しておくと、AWS CLI のコマンドにて API Gateway の REST API としてインポートできます。
ただし、その API から AWS Lambda 関数を呼び出すための、いわゆる 統合 の設定については Amazon API Gateway 固有のものであり、Open API 仕様には含まれていません。
それを実現するためには、Open API の仕様に対する API Gateway 拡張という記述方法があるので、それを使用します。
一方、AWS Lambda 関数や Amazon API Gateway の API をコードからデプロイするには、AWS SAM という強力なツールも存在します。
そこで今回は、Open API 仕様をベースに API をデザインして、それを AWS SAM を使用して Amazon API Gateway の REST API としてデプロイしてみたいと思います。
この REST API から呼び出す AWS Lambda 関数も 同じ AWS SAM テンプレートで作成する前提です。
(この記事の内容は、2023年 1月に検証した内容を基に記載しています。)
使用する Open API 仕様
今回は、/hello のパスで GET リクエストを発行すると、Python で記述した AWS Lambda関数を呼び出す API を作ります。
使用する Open API 仕様は下記です。これを demo-openapi.yaml として保存します。
openapi: "3.0.1" info: title: "demo-sam-openapi-api" version: "v1" paths: /hello: get: responses: "200": description: "200 response" content: application/json: schema: $ref: "#/components/schemas/Empty" x-amazon-apigateway-integration: httpMethod: "POST" uri: Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DemoSamOpenApiFunction.Arn}/invocations" responses: default: statusCode: "200" passthroughBehavior: "when_no_match" contentHandling: "CONVERT_TO_TEXT" type: "aws_proxy" components: schemas: Empty: title: "Empty Schema" type: "object"
上記中で、x-amazon-apigateway-integration:
で指定している箇所が Amazon API Gateway の拡張部分で、AWS Lambda 関数とのプロキシ統合を指定しています。
プロキシ統合の対象、つまり呼び出す AWS Lambda 関数自体も 同じ AWS SAM テンプレートで作成するため、uri:
で指定する AWS Lambda 関数の ARN は組込み関数 Sub を用いて設定しています。
使用する AWS SAM テンプレート
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-openapi Sample SAM Template for sam-openapi Globals: Function: Timeout: 3 Resources: DemoSamOpenApi: Type: AWS::Serverless::Api Properties: Name: demo-sam-openapi-api StageName: Prod EndpointConfiguration: Type: REGIONAL DefinitionBody: Fn::Transform: Name: AWS::Include Parameters: Location: demo-openapi.yaml DemoSamOpenApiFunction: Type: AWS::Serverless::Function Properties: FunctionName: demo-sam-openapi-function CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.7 Events: DemoSamOpenApiEvent: Type: Api Properties: Path: /hello Method: get RestApiId: !Ref DemoSamOpenApi Outputs: DemoSamOpenApi: Description: "API Gateway endpoint URL for Prod stage for DemoSamOpenApiFunction" Value: !Sub "https://${DemoSamOpenApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" DemoSamOpenApiFunction: Description: "DemoSamOpenApiFunction ARN" Value: !GetAtt DemoSamOpenApiFunction.Arn DemoSamOpenApiFunctionIamRole: Description: "Implicit IAM Role created for DemoSamOpenApiFunction" Value: !GetAtt DemoSamOpenApiFunctionRole.Arn
上記テンプレートでは、Resources:
にある DemoSamOpenApi
で API を定義しています。
その中で、Open API 仕様のファイルを指定します。
Open API 仕様のファイル ( demo-openapi.yaml ) は、この AWS SAM テンプレートのファイルと同じ場所にコピーしておきます。
なお今回は、AWS SAM テンプレート のファイルと Open API の仕様のファイルを別にしてローカルで保存していますが、Open API の仕様をそのまま AWS SAM テンプレートの中に記述することもできます。
その他の方法として、Open API の仕様ファイルを Amazon S3 バケットに保存して、AWS SAM テンプレートから その S3 の URI を指定することもできます。
AWS Lambda 関数は、今回の例では AWS SAM プロジェクトのフォルダに hello_world/ フォルダを作成し、そこに下記の Python の AWS Lambda 関数のコードを app.py として保存しています。
これは、hello world を返すだけのシンプルなコードです。
import json def lambda_handler(event, context): return { "statusCode": 200, "body": json.dumps({ "message": "hello world", }), }
AWS SAM テンプレートのデプロイと 結果確認
テンプレートの検証とビルドを行い、その後デプロイします。
sam validate
sam build
sam deploy --guided
上記では --guided
オプションをつけて sam deploy
を実行しているので、その後 対話式でデプロイが進みます。下記の時だけ y
を入力します。それ以外はデフォルトのままで大丈夫です。
DemoSamOpenApiFunction may not have authorization defined, Is this okay? [y/N]:
デプロイが完了すると、出力された中で Key が DemoSamOpenApi
の Value で示されている URL を Web ブラウザでアクセスします。
下記は例です。
Key DemoSamOpenApi Description API Gateway endpoint URL for Prod stage for DemoSamOpenApiFunction Value https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
その結果、下記のような hello world のメッセージが表示されれば成功です。
{"message": "hello world"}
やってみた所感
さて、今回やってみて成功したのですが、ひとつだけ気になる部分があります。
それは、AWS SAM テンプレートのリソースで AWS Lambda 関数の Events:
の指定です。
この Events:
で指定している内容は、Open API 仕様の中で指定している内容と同じなので、二重定義になってしまいます。
であれば、この Events:
を記述しなければいい、という発想になるのですが、記述しない場合、Amazon API Gateway の API からのアクセスを許可するという AWS Lambda 関数のリソースベースのポリシーが作成されません。
ではでは、そのポリシーを作成するように自分で明示的に記述すればいいではないかという発想になります。
確かに、下記のようなポリシー定義を AWS SAM テンプレートに追記することで Events:
の記述は不要になります。
DemoSamOpenApiFunctionPermission: Type: "AWS::Lambda::Permission" Properties: Action: lambda:InvokeFunction FunctionName: !Ref DemoSamOpenApiFunction Principal: apigateway.amazonaws.com
ただし、上記のような記述でデプロイすると、AWS Lambda 関数のトリガーとして Amazon API Gateway の API が認識されず、AWS マネジメントコンソールで AWS Lambda 関数のページを表示した場合、トリガーが表示されません。
それでも気にしない、運用上問題ない、ということであればよいのですが、どこか気持ち悪さを感じますよね。
よって、ポリシーも(自動)作成して、かつAWS Lambda 関数のトリガーとして Amazon API Gateway の API を認識させたい場合は、 Events:
を記載するのが良いかもしれません。
ただ、Open API 仕様で記述した内容と同じような内容を再び記述することになるので、混乱せず注意して記述するようにしましょう。