ngrokでローカルに立ち上げたHTTPサーバをHTTPSで公開する

最近では、HTTPサーバを立ち上げるにも、HTTPSでないとChromeでエラーとなったり、他のWebAPIと連携することができない場面が多くなってきました。また、SSL証明書もオレオレ証明書ではだめで、正規のSSL証明書である必要があったりします。
さらに、HTTPSであったとしても、ポート番号が443である必要がある場面が増えてきました。単に、静的なWebコンテンツであれば、GitHubにコンテンツをアップして、GitHub Pagesの機能を使って公開することはできるのですが、Node.js等を使ったWebAPIはできなかったりします。

そこで、ngrokを使うことで、ローカルに立ち上げたHTTPサーバを、正規のSSL証明書でかつポート番号443で公開することができ、WebAPIサーバとしても振舞えるようにします。

# ngrokアカウントの作成

まだの場合は、ngrokアカウントを作成します。以下のページからSign upします。

https://ngrok.com/

作成済みの場合は、Loginします。

# ngrokのダウンロード

ngrokを動作させるOSに合わせてバイナリをダウンロードします。
今回は、Intel CPUのUbuntuで動作させます。現時点では「ngrok-v3-stable-linux-amd64.tgz」でした。
これを解凍すると、実行形式のngrok ができます。
今回は、以下の場所に置きます。

```
~/ngrok/ngrok
```

# ngrokの実行準備

ngrokを実行するには、Authtokenが必要です。ngrokログインアカウントごとに払い出されています。ngrokにLoginして、左側のナビゲータから「Your Authtoken」を選択すると表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/c1a6e4ca-6f67-ef66-bdd0-df49cf24075e.png)

次に、ngrokを実行する前に、これをngrokを実行する予定のOSユーザに設定します。
実行したいOS上で、ngrokを実行する予定のユーザで、以下を実行します。

```
> ~/ngrok/ngrok config add-authtoken [Authtoken]
```

これにより、OSユーザのホームディレクトリの下に「~/.config/ngrok/ngrok.yml」というファイルが出来上がり、そこに先ほどのAuthtokenが書き込まれます。

# HTTPサーバの立ち上げ

それではさっそくHTTPサーバを立ち上げましょう。
今回は簡単に、以下を利用します。

https://github.com/poruruba/amplify_template

ZIPでダウンロードして、解凍し、必要なモジュールをインストールします。

```
> unzip amplify_template-master.zip
> cd amplify_template-master
> npm install
```

次に、.envファイルを作成して、ローカルで立ち上げたいHTTPのポート番号を指定します。たとえば、30080で立ち上げたい場合は以下のようにします。

```amplify_template-master/.env
PORT=30080
```

以下のようにすることで、HTTPサーバが立ち上がります。

```
> node app.js
```

ブラウザで開いてみます。

```http://[HTTPサーバを立ち上げたホスト名]:30080```

以下のファイルが表示されます。
```amplify_template-main/public/index.html```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/06b388bd-abd2-2dda-91d2-6c57752f1823.png)


# ngrokを立ち上げる

HTTPSでアクセスできるように、ngrokを立ち上げます。以下のように実行します。

```
> ~/ngrok/ngrok http 30080
ngrok                                                                                (Ctrl+C to quit)

Session Status                online
Account                       XXXXXXXX@gmail.com (Plan: Free)
Version                       3.0.2
Region                        Japan (jp)
Latency                       calculating...
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://XXXX-XXXX-XXXX-XXX-XXXX-XXXX-XXX-XXXX-XXXX.jp.ngrok.io -> http://localhost:30080

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00
```

これでHTTPSでアクセスできるようになっています。上記のForwardingで表示されているURLにアクセスしてみましょう。

# HTTPSのURLを取得表示

実は、HTTPSでアクセスできるURLは、ngrokの起動のたびに異なります。（有償プランのngrokであれば、固定にはできますが。。。）
そこで、HTTPSのURLがわかるようにします。
HTTPSのURLは以下のURLに対してHTTP Getすると得られる文字列の中にあります。

```
curl http://localhost:4040/api/tunnels
```

```
{
	"tunnels": [
		{
			"name": "command_line",
			"ID": "XXXXXXXXXXXXXXXXXXXXXXX",
			"uri": "/api/tunnels/command_line",
			"public_url": "https://XXXX-XXXX-XXXX-XXX-XXXX-XXXX-XXX-XXXX-XXXX.jp.ngrok.io",
			"proto": "https",
			"config": {
				"addr": "http://localhost:30080",
				"inspect": true
			},
			"metrics": {
				"conns": {
					"count": 4,
					"gauge": 0,
					"rate1": 0.007181222997985889,
					"rate5": 0.008537526897585294,
					"rate15": 0.0038307014878300348,
					"p50": 6853040947.5,
					"p90": 8925784161,
					"p95": 8925784161,
					"p99": 8925784161
				},
				"http": {
					"count": 19,
					"rate1": 0.03054120171725377,
					"rate5": 0.03966522214651725,
					"rate15": 0.018062001948772572,
					"p50": 3123538,
					"p90": 7467821,
					"p95": 7900887,
					"p99": 7900887
				}
			}
		}
	],
	"uri": "/api/tunnels"
}
```

public_url のところにあります。
このエンドポイントは、ローカルホストからしか呼び出せないようです。
そこで、立ち上げたHTTPサーバで、WebAPIを実装し、その中でローカルホストに対してHTTP Getします。
以下のようにフォルダを作成して、swagger.yamlとindex.jsを作成します。

```
> mkdir amplify_template-main/api/controllers/tunnels
> cd amplify_template-main/api/controllers/tunnels
> vi swagger.yaml
> vi index.js
```

```yaml:amplify_template-main/api/controllers/tunnels/swagger.yaml
paths:
  /tunnels:
    get:
      responses:
        200:
          description: Success
          schema:
            type: object
```

```js:amplify_template-main/api/controllers/tunnels/index.js
'use strict';

const HELPER_BASE = process.env.HELPER_BASE || "/opt/";
const Response = require(HELPER_BASE + 'response');

const fetch = require('node-fetch');
const Headers = fetch.Headers;
const { URL, URLSearchParams } = require('url');

exports.handler = async (event, context, callback) => {
	var response = await do_get('http://localhost:4040/api/tunnels');
	return new Response({ public_url: response.tunnels[0].public_url } );
};

function do_get(url, qs) {
  var params = new URLSearchParams(qs);

  var params_str = params.toString();
  var postfix = (params_str == "") ? "" : ((url.indexOf('?') >= 0) ? ('&' + params_str) : ('?' + params_str));
  return fetch(url + postfix, {
      method: 'GET',
    })
    .then((response) => {
      if (!response.ok)
        throw 'status is not 200';
      return response.json();
    });
}
```

これで、ポート番号30080で立ち上げたHTTPサーバにおいて、/tunnelsというエンドポイントでpublic_urlが返ってくるようになりました。

これを、HTTPサーバの静的ページから/tunnelsのエンドポイントをたたいてpublic_urlを取得して、静的ページに表示させるようにします。

以下のファイルを書き換えます。Vue 2.0を使っています。

```
amplify_template-main/public/index.html
amplify_template-main/public/js/start.js
```

以下、抜粋です。

```html:amplify_template-main/public/index.html
    <div id="top" class="container">
        <h1>Template</h1>
        
        <label>public_url</label> <a v-bind:href="public_url">{{public_url}}</a>

        <!-- for progress-dialog -->
        <progress-dialog v-bind:title="progress_title"></progress-dialog>
    </div>
```

```js:amplify_template-main/public/js/start.js
    data: {
    	public_url: ""
    },
・・・・
    mounted: function(){
        proc_load();
        
        do_get('/tunnels')
        .then(response =>{
        	this.public_url = response.public_url
        });	
    }
```

それでは、HTTPサーバを立ち上げなおして、Webページを開いてみます。

```
> node app.js
```

もう一度、ブラウザで以下を開きます。

```http://[HTTPサーバを立ち上げたホスト名]:30080```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/d40947cc-2c21-9612-7fc8-2f832563a2e2.png)

そうすると、public_urlの後に、HTTPSのURLが表示されています。それをクリックすることで、HTTPSでのアクセスに切り替わります。同じページを表示しているため、違いはないですが、アドレスバーの表示がHTTPSの表示に切り替わり、錠のマークがついているので、ちゃんとHTTPSになっていることがわかります。

# 自動起動させる

後はこれを自動起動させます。

HTTPサーバの起動にはpm2を利用し、ngrokの起動にはSystemdを利用します。

```~/pm2/pm2.config.js```

```js:~/pm2/pm2.config.js
module.exports = {
  apps : [
  {
          name: "Public_Sites(30080)",
          script: "./app.js",
          cwd: '/home/XXXX/amplify_template-master'
  }
  ]
}
```

pm2については、以下を参考にしてください。

　[pm2でNode.js実行環境を整備する](https://qiita.com/poruruba/items/10df0d94e9127797498f)

次に、ngrokを起動するためのスクリプトファイルを作成します。作成場所はどこでもよいです。以下は例です。

```~/project/cron/ngrok-startup.sh```

```sh:~/project/cron/ngrok-startup.sh
#!/bin/sh
/home/XXXX/ngrok/ngrok http 30080
```

そして、実行権限を与えます。

```
> chmod +x ngrok-startup.sh
```

ngrokのためのsystemd設定ファイルは以下のようになります。

```
# vi /etc/systemd/system/ngrok-XXXX.service
```

以下のUserのところに、ngrokを起動させるOSアカウント名を記載します。

```/etc/systemd/system/ngrok-XXXX.service
[Unit]
Description=ngrok startup
After=network.target
ConditionPathExists=/home/XXXX/projects/cron

[Service]
ExecStart=/home/XXXX/projects/cron/ngrok-startup.sh
Restart=no
Type=simple
User=XXXX

[Install]
WantedBy=multi-user.target
```

以下のようにして、Systemdで起動させます。

```
> systemctl restart ngrok-XXXX.service
```

起動したかどうかは、以下で確認できます。

```
> systemctl status ngrok-XXXX.service

● ngrok-XXXX.service - ngrok startup
   Loaded: loaded (/etc/systemd/system/ngrok-XXXX.service; disabled; vendor preset: enabled)
   Active: active (running) since Wed 2022-04-20 22:11:17 JST; 24min ago
 Main PID: 6643 (ngrok-startup.s)
    Tasks: 10 (limit: 4261)
   CGroup: /system.slice/ngrok-XXXX.service
           tq6643 /bin/sh /home/XXXX/projects/cron/ngrok-startup.sh
           mq6648 /home/XXXX/ngrok/ngrok http 30080

Apr 20 22:11:17 compact systemd[1]: Started ngrok startup.
```

以上