AWSを触り始めて間もない超初心者がLambdaでとあるサイトからスクレイピングして特定のデータを返すAPIを作成し、jQueryからCORSで呼び出そうとしたときに引っかかったことを備忘録として記します。
使用AWSサービス
Lambda
PythonやNode.jsなどいろいろな言語の関数を実行できるサーバーレス環境。
今回はPythonを利用して動画サイトにスクレイピングをかけ、動画URLが記されたメタファイルのURLを取得してそれを結果として返すために使用
API Gateway
APIエンドポイントを作成でき、AWS内の様々なサービスとの橋渡しをしてくれる。
今回はLambdaと統合し、インターネットからLambda関数を呼び出すために作成。
つまづきポイント
VPCに接続するとLambdaからインターネットに接続できない
最初、Lambda関数を作成する時にVPCに接続する設定で作成してしまい、Lambdaからurllibでopenとかしてもタイムアウトになってしまった。
調べるとVPCに接続した場合はインターネットに出るのにNAT Gatewayが必要になることが分かった。しかしNAT Gatewayを使うにはEIPが必要で、それにはお金がかかる。ほかに方法はないか調べたらそのそもVPCに接続しなければNATも必要ないということが分かった。
- Lambdaを使うにはVPCへの接続が必要だとの思い込み
- Lambda単体で使うならVPCへの接続は不要
テストは成功するのにAPI Gatewayから呼び出すとInternalSeverError
Lambdaのテストでは成功するのに、APIを外から呼び出すとInternalSeverErrorとなる。CloudWatchのログを見てもエラーは出ていないのになぜエラー?どこでエラー?
エラーログが出ないのは、エラーが起きていた場所が、Lambdaではなく、API Gatewayだから。Lambda関数の結果をAPI Gatewayに渡す時形式が合っていなかったためエラーが起きていた。
そしてLambda関数自体は正常に終了しているためLambdaのエラーログには出てこない。
AWSのドキュメント「HTTP API Lambda 統合に関する問題のトラブルシューティング」を参考にHTTP API のログを見ることで、返り値が正しくなかったということを発見。
return {
'statusCode': 200,
'body': dict
}
- dictをjson.dumps()してstringifyせず、dictのままJSONに入れて返す。Lambdaのテストではエラーにならないが、API Gatewayを通るときにエラーになる。(JSONの中のデータをいちいち文字列化はしてくれない)
dict単体で返すのはOK
return dict
ステータスコード付きで返したい場合
return {
'statusCode': 200,
'body': json.dumps(dict)
}
json.dumps()しなくてもdictのみを返す場合はエラーにならない。API Gatewayeがシリアライズしてくれる。
CORSエラー
CORSエラーを解決する前に、下記の違いを理解していなかったので解決方法をググってもそのページが言っていることが分からなかった。
- HTTP APIとREST APIの違い
- Lambda プロキシ統合と非プロキシ統合の違い
HTTPかRESTかは、タイプが「HTTP」だったのでHTTPだろうと推測、統合メニューをみるとLambda関数が表示されているので、おそらく統合されているのだろうと判断できた。
API Gatewayで CORS ヘッダーを返す
API GatewayでCORSを設定すれば、Lambdaで追加ヘッダーなどを設定する必要はないと理解
しかし、ブラウザでのCORSエラーが表示される。ネットワークリクエストを見るとOPTIONSメソッドがまず呼び出され、その後POSTが呼び出されているが両方とも失敗している。
OPTIONSメソッドはpreflightリクエストといって、実際のリクエストを送信する前にサーバーに確認をとるためのもの。preflightはいつも発生するわけではなく、一定の条件を満たさない場合に発生するらしい。今回はJSONをそのままPOSTするために、content-typeを指定していたのでこの条件に当てはまっていた。
preflightリクエストでもOPTIONSメソッドだろうが何だろうがAPI GatewayでCORSヘッダーを返す設定にしていればaccess-control-allow-originなどのCORSヘッダーはAPI Gatewayから返されるのでLambdaで何かしなくてもいいのだけどここに落とし穴があった。
Lambda関数で必要なクエリ文字列が正しくない時はステータスコード400を返すようにしていた。
→ OPTIONSメソッドの場合はクエリ文字列は渡されない
→ OPTIONSメソッドのリクエストにもエラーコード400が返される
→ その場合、access-control-allow-originヘッダーが含まれていても、ブラウザはCORSが許可されていないとみなして、リクエストが失敗する。
- ルートをANYにしているのに、OPTIONSメソッドへの対応をLambda関数内でしない
- preflightリクエストが発生する条件に合致していた
- preflightリクエストが発生しないよう「シンプルなリクエスト」にする
- preflightリクエストが発生するなら、OPTIONSメソッドがLambda関数にルーティングされているか確認
- ルーティングされているならLambda関数内で適切にOPTIONSメソッドに対応する(OPTIONSメソッドはクエリ文字列が渡されないので、メソッドを判定せずエラーコードを返している場合はメソッドごとに分けるなどする)
API GatewayのCORS対応については下記のページが参考になった。
Comments