[AWS IoT] MQTTを使用して、Lambdaからブラウザを更新する方法〜aws-iot-device-sdk(aws-iot-sdk-browser-bundle.js)を使用する場合〜

1 はじめに

AIソリューション部の平内(SIN)です。

サーバサイドの処理で、「コンテンツが変化した」などの理由で、ブラウザを更新したい場面は多いと思います。今回は、MQTTを使用して、Lambdaからブラウザをリロードしてみたいと思います。

なお、仕組みとしては、ブラウザ側のスクリプトでAWS IoTのエンドポイントをSubscribeしておき、Lambda側で何らかのメッセージをPublishします。 ブラウザ側のスクリプトは、メッセージを受け取ったタイミングで、ページをリロードするというものです。

ブラウザからのMQTTへ通信は、paho-mqttを利用したものが、比較的多く紹介されています。

<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script>

const client = new Paho.MQTT.Client(endpoint, clientId);

しかし、今回は、aws-iot-sdk-browser-bundle.jsを使用して、下記のようにブラウザ上のJavascriptからaws-iot-device-sdkrequireして使用する方法を試してみました。

<script>
    var awsIot = require('aws-iot-device-sdk');
    const deviceIot = awsIot.device({
        protocol: 'wss',
        port: 443,
        host: endpoint
        //・・・略・・・
    });

    // メッセージ到着
    deviceIot.on('message', function(_topic, payload) {

    }
    // Subscribe
    deviceIot.subscribe(topic, undefined, function (err, granted) {

    }
}
</script>    

2 EndPoint

MQTTを使用するためのEndpointは、AWS IoTで用意されています。リージョンごとにURLが決まっていますので、それをそのまま使用します。

Endpointへのアクセスは、ブラウザ側については、CognitoのPoolID、Lambda側は、LambdaのRoleにポリシーを追加することで行いますので、証明書の作成などは必要ありません。

3 Cognito(PoolIDの作成)

認証されていないIDに対してアクセスを有効にするにチェックを入れてPoolIDを作成します。

認証されていない時のロールに、下記のようなAWS IoTのトピックをSubscribeするための最低限のポリシーを追加します。ここでのPoolIDは、Javascriptに直書きされ公開されますので、ポリシーの制限には注意が必要です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:Connect",
                "iot:Receive",
                "iot:Subscribe"
            ],
            "Resource": [
                "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:client/*",
                "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:topic/Refresh_Topic",
                "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:topicfilter/Refresh_Topic"
            ]
        }
    ]
}

Resourceで指定されている topicfilter/ は、iot:Subscribe のためのもので、topic/ は、iot:Receiveのためです。

4 Credentialsの取得

AWS SDKでgetCredentialsForIdentity()を使用すると、PoolIDに与えられたパーミッションでAWSのリソースにアクセスするために、Credentials(アクセスキー・シークレット・トークンのセット)が取得可能です。これは、一時的に有効なもので、取得するたびに変化します。

なお、getCredentialsForIdentity()のパラメータに必要なIdentityIdは、getId()で取得します。こちらも、一時的なIDであり、ここにPoolIDをそんまま使用することはできません。

<br />const  AWS = require('aws-sdk');

async function getCredentials() {
    AWS.config.region = region;
    var cognito = new AWS.CognitoIdentity();
    var params = {
      IdentityPoolId: PoolId
    };
    const identityId = await cognito.getId(params).promise();
    const data = await cognito.getCredentialsForIdentity(identityId).promise();
        var credentials = {
          accessKey: data.Credentials.AccessKeyId,
          secretKey: data.Credentials.SecretKey,
          sessionToken: data.Credentials.SessionToken
        };
        return credentials;
}

5 Subscribe

Credentialsを使用して、トピックをSubscribeするコードは、以下のようになります。 先に指定したポリシーでは、clientIdは、何でも大丈夫ですが、トピック名は、固定(Refresh_Topic)になります。

const awsIot = require('aws-iot-device-sdk');
const endpoint = 'xxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com';
const clientId = 'client_id'
const topic = "Refresh_Topic"

async function job() {

    const credentials = await getCredentials(); // Credentialsの取得

    const deviceIot = awsIot.device({
        region: region,
        clientId: clientId,
        accessKeyId: credentials.accessKey,
        secretKey: credentials.secretKey,
        sessionToken : credentials.sessionToken,
        protocol: 'wss',
        port: 443,
        host: endpoint
    });

    deviceIot.on('message', function(_topic, payload) {
        console.log('>' + payload.toString());
    });

    deviceIot.subscribe(topic, undefined, function (err, granted){
        if( err ){
            console.log('subscribe error: ' + err);
        } else {
            console.log('subscribe success');
        }
    });
}

job();

6 メッセージ受信の確認

TopicにPublishされたデータが、ちゃんと受信できているかどうか(Subscribeが正常に完了しているかどうか)は、AWSコンソールから簡単に行うことができます。

AWS IoTのテストトピックへの発行を選択します。

トピック名を指定し、送信したいメッセージを入力してトピックへの発行を押すと、Subscribe側でメッセージが確認できます。

7 aws-iot-sdk-browser-bundle.js

ここまでの作業で、Cognitoを使用するために const AWS = require('aws-sdk'); とし、AWS IoTを使用するために、const awsIot = require('aws-iot-device-sdk'); と記述しました。

ブラウザから使用する場合、AWS SDKは、下記の用にロードできますが、これだけでは、残念ながらaws-iot-device-sdkは使用できません。

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script>

AWS IoTを使用するために、今回、使用させて頂いたのは、https://github.com/aws/aws-iot-device-sdk-jsBrowser Applicationsのところにある要領です。

下記の手順で、aws-iot-sdk-browser-bundle.jsというモジュールが作成できます。

$ git clone https://github.com/aws/aws-iot-device-sdk-js.git
$ cd aws-iot-device-sdk-js
$ npm install -g browserify
$ npm run-script browserize
$ ls browser/aws-iot-sdk-browser-bundle.js
browser/aws-iot-sdk-browser-bundle.js

このaws-iot-sdk-browser-bundle.jsをhtmlと同じ場所にコピーしてロードすることで、require('aws-iot-device-sdk') と書けるようになります。

<script src="aws-iot-sdk-browser-bundle.js"></script>

8 html

最終的に完成したhtmlは、以下のとおりです。

このhtmlは、読み込まれた時間を表示するだけのページです。 AWS IoTのEndpointに対して、トピック名Refresh_Topic でSubscribeし、メッセージがポストされたら、ページをリロードしています。(時間が現在時にリフレッシュされます)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Refresh Sample</title>
    <script src="aws-iot-sdk-browser-bundle.js"></script>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script>
  </head>
  <body>

      <h1 id="time">DATETIME</h1>
      <script>
        var now = new Date();
        document.getElementById("time").innerHTML = now.toLocaleTimeString();


        var awsIot = require('aws-iot-device-sdk');

        const PoolId = 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
        const region = 'ap-northeast-1';

        async function getCredentials() {
          AWS.config.region = region;
          var cognitoidentity = new AWS.CognitoIdentity();
          var params = {
            IdentityPoolId: PoolId
          };
          const identityId = await cognitoidentity.getId(params).promise();
          const data = await cognitoidentity.getCredentialsForIdentity(identityId).promise();
              var credentials = {
                accessKey: data.Credentials.AccessKeyId,
                secretKey: data.Credentials.SecretKey,
                sessionToken: data.Credentials.SessionToken
              };
              return credentials;
        }

        const topic = "Refresh_Topic";

        async function job() {
            const endpoint = 'xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com';
            const clientId = 'sample_id'

            const credentials = await getCredentials(); // Credentialsの取得

            const deviceIot = awsIot.device({
                region: region,
                clientId: clientId,
                accessKeyId: credentials.accessKey,
                secretKey: credentials.secretKey,
                sessionToken : credentials.sessionToken,
                protocol: 'wss',
                port: 443,
                host: endpoint
            });

            deviceIot.on('message', function(_topic, payload) {
                console.log('> ' + payload.toString());
                location.reload();
            });

            deviceIot.subscribe(topic, undefined, function (err, granted){
                if( err ){
                    console.log('subscribe error: ' + err);
                } else {
                    console.log('subscribe success');
                }
            });
          }

        job();

      </script>
</body>
</html>

テストしている様子です。何らかのメッセージがPublishされると、ブラウザが更新されていることを確認できます。

9 Publish側のコード

最後に、Publish側のLambdaのコードを紹介させて頂きます。Lambdaには、AWS IoTへPublishできるポリシーを追加する必要があります。

const endpoint = 'xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com';
const topic = "Refresh_Topic"
const aws = require('aws-sdk');
const region = 'ap-northeast-1';

var iotdata = new aws.IotData({
    endpoint: endpoint,
    region: region
});
var params = {
    topic: topic,
    payload: '{"action":"refresh"}',
    qos: 0
};
let result = await iotdata.publish(params).promise();

10 最後に

今回は、Lambdaからブラウザをリフレッシュする手段としてMQTTを使用しました。

ブラウザからのAWS IoTの利用も、今回紹介した、aws-iot-device-sdkを使用すれば、 比較的ハードルが下がるような気がしました。

11 参考リンク

Getting Started in a Browser Script

コメントは受け付けていません。