ngrokを無料プランでURL固定してみる


この記事は スマートスピーカー2 Advent Calendar 2018 8日目 の穴埋め記事です。

# ngrokとは
httpトンネリングサービスです。
ローカルPCで立ち上げているプログラムをポート開放したりせず、ngrokが発行するURLを経由して外部公開することができます。
さらにhttpsや他のtcpプロトコルにも対応しています。

[ngrok](https://ngrok.com/)

私はスマートスピーカースキル開発でngrokを使用しており、シミュレーターや実機テストの際にLambda等FaaSへデプロイせずにngrokでローカルでスキルのプログラムを動かしテスト/デバッグを行っています。



# なぜngrokを使うのか
理由は3つあります。

## 1. デプロイが手間
まずスマートスピーカースキルのデプロイ先としてはAWS LambdaやFirebase Functionsが一般的ですが、ソース修正する度にソース固めてデプロイするのが手間である点です。
シェルやバッチ組めばコマンドで一発ですが、その一発も手間です。

対してngrokはローカルで動いているのでデプロイもクソもありません。
ここで「プログラムの再起動でコマンド叩くだろ」と思われるでしょうが、後述するnodemonとの組み合わせで何もする必要がありません。

## 2. ログ反映が遅い
1つ目に関連しますが、クラウドではログの反映が遅いことです。
ここはぶっちゃけ、AWS（CloudWatch）は半年ぐらい前は1～2分はかかりほんと我慢ならなかったのですが、最近はわりとすぐにログ出てくれてます。
しかしラグは当然あります。

Firebaseも半年ぐらい前は同じぐらいログ反映に時間かかっていました。
（最近触ってないので今どうかはわかりません…）

対してngrokではプログラムはローカルで動いているので、当然ログは即出ます。

## 3. nodemon/ts-nodeとの組み合わせがすこぶる便利
「[nodemon](https://github.com/remy/nodemon)」とは、プログラムを実行しつつ、ソースに修正があったら自動でプロセスを再起動してくれるNode.jsツールです。
これを組み合わせることで`Ctrl + S`で修正が即反映されます。

「[ts-node](https://github.com/TypeStrong/ts-node)」は.tsファイルを自動でコンパイル実行してくれるツールです。
TypeScriptを使っている場合はこれも組み合わせることで爆速ローカル開発ができます。
（コンパイルは多少重いのでマシンスペックに依存します）

参考
[Node.jsのソース修正後、nodemonで自動再起動](https://qiita.com/twipg/items/cb969b335d66c4aee690)
[ts-nodeでCLI上でのtypescriptの検証がめっちゃ楽になった話](https://www.te-nu.com/entry/2017/11/11/184846)
[【ホットリロード】nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。](https://aknow2.hatenablog.com/entry/2018/04/09/150006)

# ngrok（無料版）の弱点
ngrokの素晴らしさは分かって頂けたかと思いますが、そんなngrokにも1点弱点があります。
ngrokには無料版/有料版があり、無料版ではURLが固定できないという点です。
ngrokのコマンド実行時に払い出されるURLのドメイン部が毎回乱数となるのです。

以下はngrokコマンド実行結果になりますが、`Forwarding`にあるURLのドメイン部が実行するたび変わります。

```
$ ngrok http 3000

ngrok by @inconshreveable                                       (Ctrl+C to quit)

Session Status                online
Account                       Miso Tanaka (Plan: Free)
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://xxxxxxxx.ngrok.io -> localhost:3000
Forwarding                    https://xxxxxxxx.ngrok.io -> localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              26      0       0.00    0.00    5.05    9.54

HTTP Requests
-------------
```



# 無料プランでURL固定する方法
「無料プランでURL固定化」自体が限りなく黒いグレーな行為であるため、以下自己責任でお願い致します。

ちなみにURLを固定化しても1点、有料版には届かない点があります。
それは起動から8時間が経過するとトンネリングが無効化されるという点です。
しかしこれは開発やデバッグで使う分には不都合ないと思います。

## 結論はAmazon API Gateway（AWS）とAWS CLI
方法は至って簡単で、AWSのAPI Gatewayでngrokが生成したURLをパススルーさせます。
そしてngrokをNode.jsから実行し、同時にAWS CLIでAPI Gatewayを更新するコマンドを叩くだけです。

以下細かい手順になります。

## API GatewayからAPIを作成
AWSコンソールよりAmazon API Gatewayを開き、「APIをの作成」ボタンを押します。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/37843e52-0ba8-ed67-f025-06ca17811aa5.png)

「API名」に「ngrok」とでも入れて（なんでもいいです）、「APIの作成」ボタンを押します。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/5ed015f0-1d8b-9158-ba5a-a83840ff54ff.png)

このような画面になるので、「アクション」ボタン > 「メソッドの作成」と選択します。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/a6201b98-d76a-1cb3-d34f-ab6f3e7b0a07.png)

リソースの「/」の下にセレクトボックスが出てくるのでとりあえず「ANY」を選択し、すぐ右にあるチェックマークを押します。
※ここは必要なメソッドに絞ってもいいです。例えばスマートスピーカースキルならPOSTを選択すれば十分でしょうし。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/f9ccc50e-d31e-6733-4dc0-0bfd23594978.png)

この画面まできたらコンソールの操作は完了です。
2箇所の赤枠部分にそれぞれ乱数のIDが入っているので、メモっておきます。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/b664b534-2297-5eae-ca8b-25ad4aa503ec.png)

## ngrok URL固定スクリプトを実行
以下のNode.jsプログラムを実行します。
前提として、Node.js、AWS CLIがインストールされていることです。

ngrokもNode.js版をインストールしていなければ入れます。

```
npm i -g ngrok
```

実行プログラムは以下になります。
先頭の`config`オブジェクトをいじります。

`restApiId`には先ほどメモした`ngrok(xxxxxxxx)`の乱数部を入れます。
`resourceId`には先ほどメモした`/(xxxxxxxx)`の乱数部を入れます。
`httpMethod`には先ほどANYを指定していればそのままで、ANY以外ならそれを指定してください。
`port`はローカル実行させるプログラムのポート番号を指定してください。

```js:index.js
const config = {
    restApiId: "xxxxxxxx",
    resourceId: "xxxxxxxx",
    httpMethod: "ANY",
    port: 3000,
}

const execSync = require('child_process').execSync
const ngrok = require('ngrok')

const main = async () => {
    const ngrokUrl = await ngrok.connect(config.port)
    console.log(ngrokUrl)
    
    execSync(`aws apigateway put-integration --rest-api-id ${config.restApiId} --resource-id ${config.resourceId} --http-method ${config.httpMethod} --type HTTP_PROXY --integration-http-method ${config.httpMethod} --uri ${ngrokUrl}`)
    execSync(`aws apigateway create-deployment --rest-api-id ${config.restApiId} --stage-name prod`)
    console.log(`https://${config.restApiId}.execute-api.ap-northeast-1.amazonaws.com/prod`)
}
main()
```

あとは実行するだけです。

```
$ node index.js
https://xxxxxxxx.ngrok.io
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod
```

返ってくる結果のうち、上の行がngrokが生成する乱数URLです。
これは一応表示していますが気にしなくていいです。

下の行が固定化されたURLになります。
`xxxxxxxxxx`の部分は`restApiId`が入ってきます。

あとは下の行のURLを開発やテストで使い回せばおっけーです。
ngrokを使う時にnodemonとあわせ上記のプログラムを実行しておきます。

以上、シェルやバッチ置いてパス通してすぐ実行できるようにしてもいいと思います。