目次
はじめに
こんにちは、omkです。
最近WebSocketで遊び始めましたが、APIGateway + LambdaでサーバレスにWebSocketを利用できるので便利ですね。
WebSocketの勝手がわからないのでなかなか正常に繋がるまでに時間がかかりました。
今回はAPIGawateyでWebSocket + Lambdaで作ったサーバからクライアントにメッセージを送信する際に「Internal server error」が表示される問題が生じたので、その際に行った対応をまとめます。
前提
Lambda
- Python3.9で作成します。
APIGateway
- 認証なし
- Lambdaプロキシ統合を利用
- パブリックで接続可能
クライアント
- wscatで確認
クライアントからサーバにルート選択式「send」で「message」を送信することで、サーバに送信した「message」の内容をサーバからクライアントに返す、というAPIを作成しました。
入力と期待する出力
> {"action": "send", "message": "hoge"}
< hoge
入力とエラーを含んだ出力
> {"action": "send", "message": "hoge"}
< {"message": "Internal server error", "connectionId":"{コネクションID}", "requestId":"{リクエストID}"}
完成したLambda関数
先に完成して動いたLambda関数を載せておきます。
import json
import sys
import boto3
def lambda_handler(event, context):
client = boto3.client(service_name='apigatewaymanagementapi',endpoint_url = "{https://~のAPIGatewayのURLとステージのパス(もしくはカスタムドメイン)}")
message = json.loads(event['body'])['message']
connectionId = event['requestContext']['connectionId']
print("connectionId: " + connectionId)
print("message: " + message)
try:
response = client.post_to_connection(
Data=message,
ConnectionId=connectionId
)
except Exception as e:
print(e)
sys.exit(1)
print(response)
return { "statusCode": 200 };
実際にこのLambda関数を作る上で発生していた問題を以下の確認観点にまとめます。
確認観点
①APIGatewayのIAM権限
APIGatewayからLambdaを実行するのにAIGatewayのIAMロールに
lambda:InvokeFunction
の権限が必要です。
②Lambdaのタイムアウト値
Lambdaのデフォルトのタイムアウト値は3秒です。
処理が終わっていない場合はLambdaがタイムアウトしますのでクライアントにはエラーが返ってきます。
③AWS SDK(boto3)のエンドポイントURL
Lambdaでメッセージを送信する際に利用していたpost_to_connection()を実行するとLambda側では
Could not connect to the endpoint URL: "https://execute-api.ap-northeast-1.amazonaws.com/@connections/{コネクションID}"
のエラーが出ていました。
これはAWSのAPIを実行する際のエンドポイントの指定が無い場合にデフォルトの
execute-api.ap-northeast-1.amazonaws.com
にPOSTしようとしているためにエラーが発生しています。
AWS公式のドキュメントを参照します。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html
ドキュメントには以下の記載があります。自分のAPIGatewayに対してPOSTする必要があります。
POST https://{api-id}.execute-api.us-east-1.amazonaws.com/{stage}/@connections/{connection_id}
よってClientの「endpoint_url」に作成したAPIGatewayのエンドポイント(カスタムドメイン)を渡してあげれば接続できます。
④LambdaのIAM権限
③の対応で自分のAPIGatewayに接続できるようにはなりましたが、引き続きpost_to_connection()を実行するとLambda側でエラーが出ます。
An error occurred (AccessDeniedException) when calling the PostToConnection operation: ~~
Lambdaに割り当てたIAMロールにpost_to_connection()の権限がないためのエラーです。
ロールに
execute-api:ManageConnections
の許可を与えることで実行できるようになります。
⑤返却するステータスコード
ここまでの対応で期待する結果が返ってくるようなりました。
ただしステータスコードで200を返していなかったので次のようになっていました。
> {"action": "send", "message": "hoge"}
< hoge
< {"message": "Internal server error", "connectionId":"{コネクションID}", "requestId":"{リクエストID}"}
これは
{ "statusCode": 200 }
をreturnするようにすることで解消されます。
完成
ここまでの対応でエラーが出ることなく欲しい結果が返ってくるようになりました。
※追記
直らない場合は他にもプログラム由来のエラーが出ていないかCloudWatchLogsを確認してみてください。
おわりに
WebSocketは初めてだったのでどうやって繋ぐのかも分からないところからスタートしましたが、振り返ってみるとAWSの設定由来のエラーばかりでWebSocket側のあれこれはAPIGateway側でやってくれているのかいい感じに使えました。
今回は動作確認のために簡単なものを作成しましたがもっと面白いものを作ってみたいですね。
以上、最後までお付き合いありがとうございました。
アーキテクト課のomkです。
AWSについて雑多に取り組んだ内容を発信しています!!