1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【AWS API Gateway】WebSocket を使用した双方向通信

Last updated at Posted at 2022-07-24

WebSocket とは

Webサーバとクライアントの間で双方向通信できるようにする技術です。通常はクライアントがリクエストをWebサーバに送り、それに応じてWebサーバがレスポンスを返しますが、WebSocketではサーバ側が任意のタイミングでクライアントにメッセージを送ることができます。

WebSocket API を作成する

バックエンドを Lambda 関数とする WebSocket API を作成します。先に Lambda 関数を作成する をやってもやらなくてもどちらでも構いません。

「WebSocket API」の「構築」をクリックします。

1.png

API 名、ルート選択式を入力します。ルート選択式とはルートキーを指定するための JSON パスです。ルートキーとは、ルートの名前です。ルートとは、API を呼び出したときの実行するアクションです。

2.png

image.png

カスタムルートを作成します。ルートキーには「sendmessage」を入力します。

3.png

Lambda 関数を指定します。指定する前に Lambda 関数を作成する必要があるので、作成していない場合は一旦適当な Lambda 関数を作成し、「前へ」→「次へ」を繰り返すと作成した Lambda 関数を選択することができます。

4.png

デプロイするステージ名を入力します。例えば開発環境であれば、development、テスト環境であれば test を入力します。

5.png

設定確認では「作成してデプロイ」をクリックします。

6.png

Lambda 関数を作成する

Lambda 関数は以下のような event を受け取ります。ここからクライアントメッセージを取り出し、加工してクライアントに返すような Lambda 関数を作成します。

{
  "requestContext": {
    "routeKey": "sendmessage",
    "messageId": "vvvvvvvvvvv=",
    "eventType": "MESSAGE",
    "extendedRequestId": "wwwwwwwwww=",
    "requestTime": "23/Jul/2022:07:09:00 +0000",
    "messageDirection": "IN",
    "stage": "production",
    "connectedAt": 1658559861848,
    "requestTimeEpoch": 1658560140781,
    "identity": {
      "sourceIp": "123.123.123.123"
    },
    "requestId": "xxxxxxxxx=",
    "domainName": "API_GATEWAY_ID.execute-api.ap-northeast-1.amazonaws.com",
    "connectionId": "yyyyyyyyyy=",
    "apiId": "API_GATEWAY_ID"
  },
  "body": "{'action': 'sendmessage', 'message': 'hello, everyone!'}",
  "isBase64Encoded": "False"
}

以下のコードを使用します。

import os
import json
import time
import boto3

def custom_function(msg):
    """
    メッセージを加工する関数
    Args:
        msg (str): 加工前メッセージ
    Returns:
        - (str): 加工後メッセージ
    """
    return 'Hello, ' + msg

def lambda_handler(event, context):
    # クライアントからのメッセージを取得する
    msg = json.loads(event['body'])['message']
    # デプロイステージ名を取得する
    stage = event['requestContext']['stage']
    # WebSocket URL のドメイン名を取得する
    domain = event['requestContext']['domainName']

    # ApiGatewayManagementApi クラスを初期化。endpoint_url を指定する。
    client = boto3.client(
        'apigatewaymanagementapi',
        endpoint_url=f'https://{domain}/{stage}'
    )

    # メッセージをカスタマイズする
    custom_msg = custom_function(msg)

    # クライアントにメッセージを送信する
    client.post_to_connection(
        Data=custom_msg,
        ConnectionId=event['requestContext']['connectionId']
    )

    return {
        'statusCode': 200,
        'body': msg
    }

Lambda 関数の IAM ロールに WebSocket API を呼び出す権限を付与します。IAM ロールのインラインポリシーに以下を追加します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "execute-api:ManageConnections",
            "Resource": "arn:aws:execute-api:ap-northeast-1:ACCOUNT_ID:*/*/POST/@connections/*",
            "Effect": "Allow"
        }
    ]
}

Lambda 関数のタイムアウトは 1 分に変更しました。これはデフォルトの 3 秒では Lambda 関数でタイムアウトしたためです。

9.png

コマンドラインで動作確認

Windows の Powershell で動作確認をしました。wscat コマンドをインストールします。

npm install -g wscat

以下のように接続し、メッセージを送信します。

PS C:\> wscat -c wss://API_GATEWAY_ID.execute-api.ap-northeast-1.amazonaws.com/production
Connected (press CTRL+C to quit)
> {"action": "sendmessage", "message": "John"}
< Hello, John

次に API Gateway でタイムアウトしてもメッセージが返ってくることが確認します。API Gateway のコンソール画面でタイムアウトを 5 秒にし、Lambda 関数でそれ以上待機してタイムアウトするようにします。

def custom_function(msg):
    time.sleep(10)
    return 'Hello, ' + msg

10.png

コマンドラインでは API Gateway でタイムアウトしたことはわからないようです。次の「ブラウザで動作確認」の場合には API Gateway でタイムアウトしたことがわかります。

ブラウザで動作確認

API Gateway でタイムアウトした後でもメッセージが届いていることが確認できます。

Qiita-gif用 (6).gif

ブラウザでの動作確認用ソースコードはこちらに置きました。

参考記事

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?