Table of Contents
背景
AWS SAMでせっせとサーバーレスな処理を構築していました。
AWS Quick Start TemplatesのHello World Exampleから作成した経緯があって、デフォルトでAPI GatewayがREST APIタイプになっていたのですが、HTTP APIという別のタイプを先輩から教えていただき、そちらに変更することになりました。
そのREST→HTTPの変更とCORS設定に取り組んでいたのですが、こまごまと罠があってややハマっていました。そんな迷える子羊は僕だけではなかったようで、aws/aws-sam-cliのissueに困っている方々のコメントが寄せられていました。最終的にうまく行ったので、彼らにも届け!という思いで整理をしておきたいと思います。
ちなみにREST APIでのCORS設定はこちらのQiita記事(AWS SAMのCORSを設定する(REST APIバージョン))にまとめてあります。
HTTP APIとREST API
当初はそもそもAPI Gatewayにタイプがあったことすら知らなかった私ソリューションアーキテクトアソシエイトなのですが、HTTP APIの方が低レイテンシーで低コストだそうで、機能の詳しい違いは公式ドキュメントにまとまっています。
REST APIタイプの方が歴史が深く、現時点ではより多機能で、料金も高いようです。(料金表)
タイプ | リクエスト数 (月間) | 価格 (100 万あたり) |
---|---|---|
HTTP API | 最初の 3 億 | 1.29USD |
REST API | 最初の 3 億 3,300 万 | 4.25USD |
実装
REST APIのCORS設定テンプレート例
比較対象として、SAMのデフォルトのAPI Gatewayである、REST APIでのtemplate.yaml例を先に載せておきます。
Globals:
Function:
Runtime: ruby2.7
Timeout: 3
Resources:
HelloWorldLogsApi:
Type: AWS::Serverless::Api
Properties:
StageName: 'dev'
Cors:
AllowOrigin: "'http://127.0.0.1:3000'"
AllowCredentials: true
AllowMethods: "'POST'"
AllowHeaders: "'Content-Type,X-CSRF-TOKEN'"
HelloWorldLogsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world_logs/
Handler: app.lambda_handler
Runtime: ruby2.7
Events:
ApiEvent:
Type: Api
Properties:
RestApiId: !Ref HelloWorldLogsApi
Path: /hello_world_logs
Method: post
HTTP APIのテンプレート例
いよいよHTTP APIです。
基本的にはAWS::Serverless::HttpApiのドキュメントに書いてありますが、試行錯誤の末うまくいったテンプレートを実装例として示します。
Resources:
# API Gatewayを明示的に書く
HelloWorldApi:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionBody: # ここが必要らしい
openapi: 3.0.1
info:
title: !Ref 'AWS::StackName'
paths: {}
CorsConfiguration: # Cors設定はKey-Value形式ではなくリスト形式で書く
AllowOrigins: # AllowOriginsは複数形!(REST APIでは単数形AllowOrigin)
- "*"
AllowCredentials: true
AllowMethods:
- POST
AllowHeaders:
- Content-Type
- X-CSRF-TOKEN
HelloWorldLogsFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Join
- '-'
- - hello_world
- !Ref Stage
CodeUri: hello_world_logs/
Handler: app.lambda_handler
Runtime: ruby2.7
Events:
HelloWorldLogs: # => ここは何でも良さげ。ドキュメントはApiEvent:となっている
Type: HttpApi # 追加
Properties:
ApiId: !Ref MyApi # RestApiId→ApiId追加
Path: /hello_world_logs
Method: post
REST APIとの違いを踏まえつつポイントを。
- ResourcesにAPI Gatewayを明記する。SAMのデフォルトがRESTらしく、何も書かないと
AWS::Serverless::Function
、つまりREST APIが作られるようです。 DefinitionBody
プロパティは、AWSドキュメントではRequired: Noになっていますが、僕は入れないとエラりました。- REST APIでのCORS設定プロパティは
Cors
ですが、HTTP APIではCorsConfiguration
になる。 - CORSのAllow兄弟は、
Key: Value
形式ではなく-
を使ったリスト形式で書く。 - REST APIでは、単数形
AllowOrigin
だったのが、HTTP APIではAllowOrigins
と複数形になる。 Properties
のRestApiId
をApiId
にする。
AWSドキュメント「HTTP API の CORS の設定」
CORSのheadersをLambdaのレスポンスに含める
ちなみに、RESTとHTTPの違いとはあまり関係ありませんが、LambdaのレスポンスヘッダーにもCORSの設定を書いてあげる必要があります。
def lambda_handler(event:, context:)
{
statusCode: 200,
body: {
message: 'hello_world'
}.to_json,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Content-Type,X-CSRF-TOKEN",
}
}
end
最後に小ネタ:一度デプロイしたらAPIタイプは変更できないぽい
一度SAMプロジェクトをデプロイして、APIのタイプを変えて再デプロイしようとすると下のようなエラーになります。
Waiting for changeset to be created.. Error: Failed to create changeset for the stack: HelloWorldStack, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Update of resource type is not permitted. The new template modifies resource type of the following resources: [HelloWorldApi]
なので、一旦スタックを削除してからデプロイするのが吉なようです。