使用例を以下に示す。
Endpoints:
http://192.168.11.39:8091/google-home-notifier
https://XXXXXXXX.ap.ngrok.io/google-home-notifier
GET example:
curl -X GET https://XXXXXXXX.ap.ngrok.io/google-home-notifier?text=Hello+Google+Home
POST example:
curl -X POST -d "text=Hello Google Home" https://XXXXXXXX.ap.ngrok.io/google-home-notifier
ここで、EndpointにIP指定とホスト名指定の2通りがある。通常、発話PUSHサーバはWiFiネットワーク内(したがってプライベートIP空間)で動くので、インターネット側からの発話依頼に応えられない。
そこで、インターネット側からのリクエストも受け付けられるようにngrokを利用する。上記の"https://XXXXXXXX.ap.ngrok.io/"はngrokによって払い出されたURLである。ここでURLの"XXXXXXXX"の部分は、利用しているngrokのアカウントにログインし、Online Tunnel Endpoints(メニューから[Endpoints]→[Status]を選択)を表示すれば確認できる(下図)。
| 図1.ngrokのOnline Tunnel Endpoints |
このngrokが払い出すURLを使えばインターネット側からでも発話PUSHサーバに発話要求を送ることができる。
ところが、このngrokは、発話PUSHサーバを起動するたびに 払い出すURLを変える。したがって、それに伴って、呼び出し側のプログラムも修正しなければならない。これは甚だ不便である。そこで、新しいURLが払い出される都度、その新しいURLを呼び出し側に通知する仕組みを組み込む。
発話PUSHサーバにリクエストを送るGAS WebAPIは、WebAPI (A2)のみである。したがって、WebAPI (A2)にngrokのURLの変更を受け付ける機能を追加する。変更通知を受け取ったGASは、Googleスプレッドシートファイル"pill-reminder"の「設定」シートのセル(7行2列)にURLを書き込む。
WebAPI (A2) のPOSTメッセージを受け取る関数は次のようになる。
/**
* IFTTTからのリクエストに応じてGoogleカレンダーを服用済みに更新する
* @param {Object} e POSTメソッドで送られてくるデータ
* delay: 10 (開始を10分間遅らせる)
* timing: [朝|昼|夜|就寝前]
* ngrok_url: https://XXXXXXXX.ap.ngrok.io/google-home-notifier
*
* https://script.google.com/macros/s/AKfy............................nIbMw/exec
*/
function doPost(e) {
debug(JSON.stringify(e, null, ' '));
var params = JSON.parse(e.postData.getDataAsString()); // ※
debug('params=\n' + JSON.stringify(params, null, ' '));
if (params.timing) {
//カレンダーのタイトルと色を変える
var timing = params.timing; // => [朝|昼|夜|就寝前]が取れる
NotifyNotTaking(timing);
} else if (params.delay) {
//開始時刻を遅らせる
var delay = parseInt(params.delay); // => 遅らせる時間[分]
// どうやって遅らせる対象のイベントを見つけるか?
delayStartTime(delay);
} else if (params.ngrok_url) {
//ngrokの払い出したurlをGoogleスプレッドシートに書き込む
setNgrokUrl(params.ngrok_url);
}
var out = ContentService.createTextOutput();
//Mime TypeをJSONに設定
out.setMimeType(ContentService.MimeType.JSON);
//JSONテキストをセットする
result = {
status: 'OK'
};
out.setContent(JSON.stringify(result));
return out;
}
なお、リクエストする側は、下記のようなJSONフォーマットのデータをPOSTする。
{
"ngrok_url": "https://XXXXXXXX.ap.ngrok.io/google-home-notifier"
}
ngrokの払い出したurlをGoogleスプレッドシートに書き込む関数setNgrokUrlを以下に示す。
/**
* ngrokのurlをGoogleスプレッドシートに書き込む
* Googleスプレッドシート名:pill-reminder
* セル:7行2列
* @param {String} url ngrokが払い出したurl
*/
function setNgrokUrl(url) {
var spreadsheet = SpreadsheetApp.openById('1TRT..........................TbbY');
var sheet = spreadsheet.getSheetByName('設定');
sheet.getRange("B7").setValue(url);
}
また、Googleスプレッドシートからngrokが払い出したurlを取得する関数getNgrokUrlを以下に示す。
/**
* ngrokのurlをGoogleスプレッドシートから読み込む
*
*/
function getNgrokUrl() {
var spreadsheet = SpreadsheetApp.openById('1TRT..........................TbbY');
var sheet = spreadsheet.getSheetByName('設定');
return sheet.getRange("B7").getValue();
}
ベアボーンサーバ上で動く発話PUSHサーバ(example.js)の修正
ベアボーンサーバ上で動く発話PUSHサーバ(example.js)は、起動時、リスト3に示すJSONフォーマットのデータをWebAPI (A2) へ送信する必要がある。そこで、ngrok url払い出し処理部分を以下のように修正した。
app.listen(serverPort, function () {
putLog('Endpoints:');
putLog(' http://' + ip + ':' + serverPort + '/google-home-notifier');
connectNgrok(serverPort).then(url => {
var ngrok_url = url + '/google-home-notifier';
putLog(' ' + ngrok_url);
putLog('GET example:');
putLog('curl -X GET ' + ngrok_url + '?text=Hello+Google+Home');
putLog('POST example:');
putLog('curl -X POST -d "text=Hello Google Home" ' + ngrok_url);
sendNgrokUrl(ngrok_url);
});
})
/**
* ngrokを非同期で起動
*/
async function connectNgrok(port) {
try {
let url = await ngrok.connect({
addr: port,
region: 'ap',
configPath: '~/.ngrok2/ngrok.yml',
authtoken: token
});
return url;
} catch (err) {
putLog(err.name + ': ' + err.message);
}
}
/**
* ngrokが払い出したurlをWebAPI(A2)を使ってGoogleスプレッドシートへ書き込む
* Googleスプレッドシート: semi2018kumwのpill-reminder
*
*/
function sendNgrokUrl(ngrok_url) {
var WebAPI_A2_URL = "https://script.google.com/macros/s/AKfy...............................nIbMw/exec";
webclient.post({
url: WebAPI_A2_URL,
headers: {
"content-type": "application/json"
},
body: JSON.stringify({"ngrok_url": ngrok_url})
}, function (error, response, body){
if(!error) {
putLog("****** sendNgrokUrl 成功 ******");
} else {
putLog("****** sendNgrokUrl エラー ******");
putLog('error:' + error);
putLog('response:' + response);
putLog('body:' + body);
}
});
}
app.listen関数の最後で引数に払い出されたurlを指定してsendNgrokUrl関数を呼び出す。この関数はWebAPI (A2)を使って払い出されたurlをGoogleスプレッドシートへ書き込む。
問題点
修正したプログラムは機能した。すなわち、発話PUSHプログラムを起動するとngrokで払い出されたurlがGoogleスプレッドシートに書き込まれた。
ところが問題が発生した。それは
$ forever start example.jsを使って発話PUSHプログラムを起動したときである。
初回は無事ngrokからurlの払い出しを受けるものの、強制的にexample.jsをkillすると、確かにexample.jsは再び起動されるが、そのときngrok.connectでエラーが発生する。
Error: connect ECONNREFUSED 127.0.0.1:4040そのため、Googleスプレッドシートには
undefined/google-home-notifierが設定され、利用できなくなる。
これは、経験的にngrokのダッシュボードからURLを削除してから再び発話PUSHサーバを起動するとうまくいくことがわかっている。おそらく払い出されているurlを解放するまでに多少のタイムラグあるためだと考えられる。
ngrok管理画面
ngrokには管理画面があり、ここから現在払い出されているurlを確認できる。
| 図2.ngrok管理画面(ダッシュボード) |
この画面を出すには、 ログインして左のメニューから[Status]を選ぶ。すると、現在利用可能な払い出しurlを確認することができる。
発話PUSHサーバ(example.js)の自動起動
システム起動時に発話PUSHサーバ(example.js)を自動起動するには、/etc/rc.localに下記の1行を追加すればよい。
forever start /home/mtanaka/pill-reminder/example.js
foreverコマンドはNode.jsで作成されたアプリをデーモン化するコマンドである。/etc/rc.localで起動したforeverコマンドは管理者権限で実行されることになるので、foreverコマンドでログの確認をする際は、管理者権限でコマンドを実行しなければならない。
mtanaka@semi2014vpn:~$ sudo su root@semi2014vpn:/home/mtanaka# forever list info: Forever processes running data: uid command script forever pid id logfile uptime data: [0] X31a /usr/local/bin/node /home/mtanaka/pill-reminder/example.js 1364 1398 /root/.forever/X31a.log 72:6:20:38.561999999918044
SSHのngrokによるトンネル化
遠隔地からベアボーンサーバを管理するため、ngrokを使ってsshに外部からアクセスできるようにする。そのために、/etc/rc.localに下記の一行を追加する。
su - mtanaka -c '/home/mtanaka/ngrok tcp 22'
ngrokのダッシュボードでURLが確認できるので、それを使ってTeratermで接続すればよい。
0 件のコメント:
コメントを投稿