はじめに
既存リポジトリとしてHTTP APIのクイック作成を活用したCDKプロジェクトがある。
クイック作成の場合$defaultのルート定義が必須になったり、CDKのAPIリソース定義時に(おそらく$defaultに対する)実行ロールを割り当てることができる。
今回は$defaultルートが不要だったので、クイック作成を廃止するように修正した。
その際の躓きポイントを書く。
ちなみに$defaultルートを廃止したかった意図は下記の通り。
API Gatewayの各ルートにマッチしない場合、標準ではnot foundがレスポンスされる。
がしかし、当たり前ではあるが$defaultが定義されているとこれがマッチするので、その統合先Lambaなどでnot foundをレスポンスする実装が必要になる。
自分の認識としてAWS開発では、できるだけビジネスロジックのコード以外は書かない(マネージドサービスへ移譲)、可能ならばフローの前段で処理できるならば前段に寄せることが良い(オフロード?)と考えているので、not foundの処理はAPI Gatewayで完結するべきだと考えた。
クイック作成について:
Quick create You can use quick create to simplify creating an HTTP API. Quick create creates an API with a Lambda or HTTP integration, a default catch-all route, and a default stage that is configured to automatically deploy changes. For more information, see Create an HTTP API by using the AWS CLI.
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-basic-concept.html
As-isのコード
コードは要点のみ抜粋。
クイック作成のパラメーターとして、 credentials_arn
と target
がある。
targetは下記のようにinvocations専用のarnにする必要があるのかもしれないが、
手元で試したところlambda_fn.function_arnのみでも動作した気がする。
Lambda または Lambda プロキシ統合を設定するときは、Lambda 関数を呼び出すための Amazon リソースネーム (ARN) を統合リクエスト URI 値として割り当てます。この ARN は以下の形式になります。
arn:aws:apigateway:api-region:lambda:path//2015-03-31/functions/arn:aws:lambda:lambda-region:account-id:function:lambda-function-name/invocations
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/integration-request-basic-setup.html
arn:aws:apigateway:*api-region*:lambda:path/
の後の部分、つまり、/2015-03-31/functions/arn:aws:lambda:*lambda-region*:*account-id*:function:*lambda-function-name*/invocations
は、Lambda の Invoke アクションの REST API URI パスです。API Gateway コンソールを使用して Lambda 統合を設定する場合は、API Gateway によって ARN が作成され、統合 URI に割り当てられます。ただし、その前にリージョンから*lambda-function-name*
を選択するように求められます。
from aws_cdk import (
aws_apigatewayv2 as apigwv2,
)
// 省略
api = apigwv2.CfnApi(self, 'API',
name="X API",
protocol_type="HTTP",
credentials_arn=api_role.role_arn,
target="arn:aws:apigateway:" + self.region
+ ":lambda:path/2015-03-31/functions/"
+ lambda_fn.function_arn + "/invocations"
)
To-beのコード
コードは要点のみ抜粋。
ステージはクイック作成の場合と同じでよいので下記のとおりにする。
実行ロールは各CfnIntegrationごとにcredentials_arnを設定する。 もしくは、各Lambdaにリソースベースポリシーを設定する。
from aws_cdk import (
aws_apigatewayv2 as apigwv2,
)
// 省略
api = apigwv2.CfnApi(self, 'API',
name="X API",
protocol_type="HTTP",
)
integration = apigwv2.CfnIntegration(self, "Integration",
api_id=api.ref,
integration_type="AWS_PROXY",
integration_uri="arn:aws:apigateway:"
+ self.region
+ ":lambda:path/2015-03-31/functions/"
+ lambda_fn.function_arn + "/invocations",
payload_format_version="2.0",
credentials_arn=api_role.role_arn,
)
apigwv2.CfnStage(self, "ApiStage",
api_id=api.ref,
stage_name="$default",
auto_deploy=True,
)
躓きポイント
そもそもAs-isのCfnApiがクイック作成で実装されていることが最初わかっていなかった。
最初に修正した箇所は、CfnApiのtargetの削除である。
このときのデプロイ時のエラーが抽象的すぎて、かつweb検索でも情報が少なすぎて原因の特定に苦労した。
| CREATE_FAILED | AWS::ApiGatewayV2::Api | XStack/API (API) Resource handler returned message: "Target is a required property in this context (Service: AmazonApiGatewayV2; Status Code: 400; Error Code: BadRequestException;
Target is a required property in this context
この文言だと何が原因なのか全くわからなかった。
結論はCfnApiにcredentials_arnが定義されている→つまりクイック作成が有効→このコンテキストではtargetは必須というものだった。
各種ドキュメントをしっかりと読めば、credentials_arnはクイック作成のためのパラメーターであることは記載されている。
試行錯誤の流れとして下記のとおり。
- とりあえず$defaultルートを削除したいので、targetを削除
- 意味がわからないエラーがでる
- targetプロパティのドキュメントを読む
- クイック作成で実装されていることが判明する
- 色々なパラメーターをいじってみる
- ドキュメントを再度じっくり読むとcredentials_arnもクイック作成専用のパラメーターということがわかる
- 両方削除してデプロイ
- クイック作成で自動作成される$defaultステージがないことでエラー
- $defaultステージを定義
またノイズとなるようなコードがあり、根本原因の特定に苦戦した。
- クイック作成なのに、CfnDeploymentでクイック作成で定義される設定と同じ$defaultを定義してる(クイック作成とは異なる設定値のステージ定義ならば問題ない)
- integration_method=’POST’が定義されている→WebSocketの場合に必要なパラメーター
おわりに
クイック作成について知っていれば、今回のように躓くことはなかったかもしれない。
だが比較的ニッチな機能を全て把握するのは現実的ではないと思うので
主によく使うサービスぐらいは詳細な機能まで把握できると、開発をスムーズに進めることができそうだと感じた。
余談
今回はL1で実装されているが、L2のHttpApiならばもっと抽象化されいるはずなので簡単に実装できそうだなと思っている。機会があればL2に移行したい。実装当初は、おそらくL2がリリースされてなかったのではないかと思う。
既存の$defaultルートに統合されていたLambdaのコードについても軽く触れる。
CORSの対応をするコードが実装されていた。
これはAPI Gatewayの機能で対応できるので不要とした。
当時、特殊な要件で細かいハンドリングが必要なCORS要件があったのかもしれないが。
REST APIのプロキシリソースのようなgreedy path variable({proxy+})がHTTP APIでも利用できることを今回調べていく中で把握した。
少し前に使おうとしてできなかった記憶があったのだが勘違いしていたみたい。
$defaultとルートレベルの/{proxy+}の使い分けがしっくり来ていないので、機会があれば理解していきたい。
2つとも定義すると、/{proxy+}にしかルーティングされないはずで、その場合$defaultはどう扱うべきなのか?みたいなことがしっくりきていない。