Help us understand the problem. What is going on with this article?

AWSでRESTで送信したものをWebSocketでリアルタイムに受け取るサーバレスシステム

はじめに

オンラインイベントで応援ボタンなど押した時に、現地で一方向の情報として受信し、それをトリガーにエフェクトなど表示したいことがあります。
方法としてはSocket通信や、MQTTなどを利用したり、ngrokなどのローカル環境をネット上で受けれるようにするサービスを使うなどあります。
ただ、クライアントが数百人など多数の環境になるとソケットを大量に張る必要がある、サービスの上限制限や毎回URLが変わるなど運用が面倒なことなどが考えられます。
今回はそれらを回避しつつ、サーバレス環境でコストも少なくしたシステムを作ってみます。

全体構成

image.png

送信する方はRESTで送ることで接続数に依存せずコストを下げることが可能です。
特にいつ押されるかわからないため、無駄にSocketなどを張って時間課金のコストを増やすということをしないようにします。
受信側は送られたらPUSHで情報を受けたいのでここのみSocketでAWSと接続し通信を行います。
通常はSocketが切られた場合にConnectionIdの削除など他にも付け加えるべきものがありますが、Qiita用の簡易的なものにしていますのでご了承ください。

DynamoDBの設定

今回は2つのLambdaでconnection_idという値を共有します。
設定は以下のようなものを作成します。
image.png
作成後に
type:"admin"
というデータを1ついれておいてください。
ここに入る値を利用します。

Lambdaの作成

Lambdaを二つ作成します。

Socket用Lambda

受信画面がSocket張ったときのAPI Gatewayのconnection_idを保存するためのものです。

const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10'});
exports.handler = async event => {
  try {
    await documentClient.update({
      TableName: 'table',
      Key: {
        'type': 'admin'
      },
      UpdateExpression: "set connectionId = :c",
      ExpressionAttributeValues: {
        ":c": event.requestContext.connectionId
      },
      ReturnValues:"UPDATED_NEW"
    }).promise();
  }
  catch (err) {
    console.log(err);
    return { statusCode: 500, body: 'Failed : ' + JSON.stringify(err) };
  }
  return { statusCode: 200, body: 'Connected.' };
};

中継用Lambda

RESTを受けAPI GatewayのSocketに中継するためのものです。
あとでAPI GatewayのSocketサーバを構築したあとにendpointのURLはそれに合わせて書き換えする必要があります。

const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10' });

exports.handler = async(event) => {
    const apigwManagementApi = new AWS.ApiGatewayManagementApi({
        apiVersion: '2018-11-29',
        endpoint: 'XXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/default'
    });
    try {
        const dbRes = await documentClient.get({
            TableName: 'table',
            Key:{
            'type': 'admin'
            }
    }).promise();
        await apigwManagementApi.postToConnection({
            ConnectionId: dbRes.Item.connectionId,
            Data: JSON.stringify({ "send":"ok" })
        }).promise();
    } catch (e) {
        console.log(e);
    }
    const response = {
        statusCode: 200,
        body: '{"error":false}',
    };
    return response;
};

API Gatewayの設定

こちらもRESTとWebSocketで受けるものを二つ作る必要があります。

Socket用API Gateway

作成を押しWebSockegt APIのものを構築します。
image.png
ルート選択式は今回は利用しませんので、デフォルトであるように[$request.body.message]とでもしておいてください。
作成が終わるとWebSocketが接続した場合にどうするかの設定があります。今回は接続時のみ使いたいので$connectの(+)を押して、先ほど作成したSocket用Lambdaを接続してください。
以下のような形になっていたら、アクション>APIのデプロイで公開してください。
image.png

ここで作成されたアドレスを中継用Lambdaのendpointに記載してください。

中継用API Gateway

こちらはHTTP APIで構築します。
このあたりはよくあるものなので他のQiitaの記事や、Lambda側の入力に新規で紐付けするなどしてみてください。
テストでは必要ありませんが、CROSSの設定を忘れないように設定してください。

HTMLの作成

よくあるWebSocketの接続のページを作成します。
WebSocket接続は他のQiitaなどを参照してください。

// socket
function connect() {
    const socketServerUrl = "wss://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/YYYYYY";
    let socket = new WebSocket(socketServerUrl);

    socket.onopen = function (e) {
        console.log(e);
    };
    socket.onmessage = function (e) {
        const d = JSON.parse(e.data);
        console.log(d);
    };
    socket.onclose = function (e) {
        console.log(e);
    };
    socket.onerror = function (e) {
        console.log(e);
    };
}

まとめ

作成したHTMLをブラウザで開き、中継用API GatewayのURLを叩くと{"send":"ok"}という値が飛んできてるのがわかると思います。
この中継用API GatewayのURLをよくあるAjaxの通信で叩くことでブラウザ側に随時イベントが飛んできますので、いろいろなものに利用することができます。
これだけの作業と行数のプログラムでsocket通信がネット上に公開してる格安システムができるので、ぜひいろいろと使ってみてください。

注意

API GatewayのWebSocketは接続して放置していると10分程度で切断されます。定期的に通信させるか、切断後にリトライ方法など検討しておく必要があります。
それと、受信側のHTMLにはボタンが押されてから1秒程度の遅延が発生します。ここをもっと短くする方法などはコストの兼ね合いなどがありますので、いろいろな他のサービスなども検討してみてください。

余談

途中から雑になってるのは、そう、あなたの思った通り!細かい記事を書いて疲れたからです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした