この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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-sdkをrequireして使用する方法を試してみました。
<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-jsのBrowser 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を使用すれば、 比較的ハードルが下がるような気がしました。