使用例を以下に示す。
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 件のコメント:
コメントを投稿