年末年始Webアプリ開発自習の記録3: Node.jsをサーバーで動作させる

#はじめに
**[連休を機に考える、怠惰な私の自習戦略][Plan]**にて立てた計画に沿った自習の記録です。
前回：**[年末年始Webアプリ開発自習の記録2: Node.jsでHabiticaにPOSTを送るWebアプリケーション作成][Prev]**


##サーバー環境
**[Node.jsのバージョンを管理する](https://qiita.com/satoyan419/items/56e0b5f35912b9374305)**を参考にnvmをインストール
**[GitHub - creationix/nvm](https://github.com/creationix/nvm#installation)**を見るのが早い

```shell-session
# nvm ls-remote
...省略
       v10.10.0
       v10.11.0
       v10.12.0
       v10.13.0   (LTS: Dubnium)
       v10.14.0   (LTS: Dubnium)
       v10.14.1   (LTS: Dubnium)
       v10.14.2   (LTS: Dubnium)
       v10.15.0   (Latest LTS: Dubnium)
        v11.0.0
        v11.1.0
        v11.2.0
        v11.3.0
        v11.4.0
        v11.5.0
        v11.6.0

# nvm install v10.15.0
Downloading and installing node v10.15.0...
Downloading https://nodejs.org/dist/v10.15.0/node-v10.15.0-linux-x64.tar.xz...
######################################################################## 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v10.15.0 (npm v6.4.1)
Creating default alias: default -> v10.15.0

# node -v
v10.15.0

# npm update -g npm
/root/.nvm/versions/node/v10.15.0/bin/npm -> /root/.nvm/versions/node/v10.15.0/lib/node_modules/npm/bin/npm-cli.js
/root/.nvm/versions/node/v10.15.0/bin/npx -> /root/.nvm/versions/node/v10.15.0/lib/node_modules/npm/bin/npx-cli.js
+ npm@6.5.0
added 2 packages from 1 contributor and updated 14 packages in 7.826s
```

#サーバー設定
とりあえず動くものができたので、サーバー上で動かすように設定します。しれっとhttpsです。

##Nginxの設定
既存の設定を基に下記を追加しました。

```nginx:default.conf
server {
    listen 80;
    server_name dev.kiyo.space;
    charset utf-8;
        location / {
      return 403;
    }

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

server {
    listen 443 ssl http2;
    server_name dev.kiyo.space;
    charset utf-8;
    root /var/www/dev.kiyo.space;

    #ssl_certificate      /etc/nginx/dev.kiyo.space.crt;
    #ssl_certificate_key  /etc/nginx/dev.kiyo.space.key;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout  5m;
    ssl_dhparam /etc/nginx/dhparam.pem;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    #ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';
    #ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    ssl_prefer_server_ciphers   on;

    location /RestRelayService {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
```

##Let's encrypt!
certbot-auto だけで楽々設定できました。これすごいですね。

```
# certbot-auto
Saving debug log to /var/log/letsencrypt/letsencrypt.log

How would you like to authenticate and install certificates?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Apache Web Server plugin (apache)
2: Nginx Web Server plugin (nginx)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Plugins selected: Authenticator nginx, Installer nginx

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: dev.kiyo.space
2: xxx.xxx
3: xxx.xxx.xxx
4: xxx.xxx.xxx
5: xxx.xxx
6: xxx.xxx
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for dev.kiyo.space
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/conf.d/default.conf

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/conf.d/default.conf

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://dev.kiyo.space

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=dev.kiyo.space
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/dev.kiyo.space/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/dev.kiyo.space/privkey.pem
   Your cert will expire on 2019-04-01. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again with the "certonly" option. To non-interactively renew *all*
   of your certificates, run "certbot-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
```
忘れずにcronに登録しておきます

```
# vi /etc/cron.d/letsencrypt

00 4 1 * * root /usr/bin/certbot-auto renew --post-hook "systemctl restart nginx"
```

##RestRelayServiceをCloneしてくる
GitHubにあがってるのでCloneしてConfigを設定するだけです。
もうちょっとちゃんとしたものを作り始めるときにはDeploy方法も調べていきたいです。

```
# git clone git clone https://github.com/dmorita0108/Study-Node.git Study-Node
```

#試しに動かしてみる
デーモン化を考える前にこれでちゃんと動くのか試してみます。

```
# node main.js
```

##Path識別方法では限界
URLは下記のようになりました。Rest Clientからアクセスすると想像していた通りのことが起きました。
https://dev.kiyo.space/RestRelayService/10732D14-C569-40F3-AECE-83B06BEC9BE1
ログの出力でpathにRestRelayServiceが含まれていて思った通りに動きません。

```
Server started
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/RestRelayService/10732D14-C569-40F3-AECE-83B06BEC9BE1',
  path: '/RestRelayService/10732D14-C569-40F3-AECE-83B06BEC9BE1',
  href: '/RestRelayService/10732D14-C569-40F3-AECE-83B06BEC9BE1' }
```

##IDをQueryにする
Nginxの設定を漁った挙げ句、そもそもPathで判断するからいけないことに気付きました。
大したことはしないのでGETメソッドでIDをクエリとして渡せばそれで解決するはずです。
main.jsを書きかえます。ついでにvarを撲滅しました。

```javascript:main.js
const http = require("http");
const https = require("https");
const url = require("url");
const config = require("./config.json");

const server = http.createServer((req, res) => {
    if (req.method == 'GET') {
        let options;
        const url_parts = url.parse(req.url, true);
        console.log(url_parts);

        if (url_parts.query.id != undefined){
            console.log(url_parts.query.id);
            config.forEach(element => {
                if (url_parts.query.id == element.id) {
                    console.log("element found");
                    options = element.options;
                }
            });
        }

        if (options != undefined) {
            const relayReq = https.request(options, (relayRes) => {
                let chunks = [];

                relayRes.on("data", (chunk) => {
                    chunks.push(chunk);
                    console.log("chunk pushed");
                });

                relayRes.on("end", () => {
                    const body = Buffer.concat(chunks);
                    console.log(body.toString());
                    if(relayRes.headers['set-cookie'] != undefined) {
                        options.headers['cookie'] =  relayRes.headers['set-cookie'];
                        console.log("Set Cookie");
                    }

                    res.writeHead(relayRes.statusCode, relayRes.headers);
                    res.write(body.toString());
                    res.end();
                });

                relayRes.on("error", (e) => {
                    console.log("Error: " + e.message);
                    res.write(e.message);
                    res.end();
                });
            });

            console.log(options.toString());
            relayReq.end();
        } else {
            res.writeHead(400, {"Content-Type": "application/json"});
            res.write(JSON.stringify({
                "success": false,
                "error": "Bad Request",
                "message": "Wrong request."
            }));
            res.end();
        }
    } else {
        res.end();
    }
});

server.listen(3000);
console.log("Server started");
```
いくらIntelliSenseのためとはいえ、コールバック関数が肥大化しすぎですね。

##再チャレンジ
変更はGitHubにPushしているので、Pullして再度チャレンジします。

```
# node main.js

Server started
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?id=10732D14-C569-40F3-AECE-83B06BEC9BE1',
  query:
   [Object: null prototype] { id: '10732D14-C569-40F3-AECE-83B06BEC9BE1' },
  pathname: '/RestRelayService/',
  path: '/RestRelayService/?id=10732D14-C569-40F3-AECE-83B06BEC9BE1',
  href: '/RestRelayService/?id=10732D14-C569-40F3-AECE-83B06BEC9BE1' }
10732D14-C569-40F3-AECE-83B06BEC9BE1
element found
[object Object]
chunk pushed
{"success":true,"data":{"delta":0.9476609568673467,"_tmp":{"quest":{"progressDelta":1.0139972238480612},"streakBonus":0.09855673951420418},"buffs":{"str":0,"int":0,"per":0,"con":0,"stealth":0,"streaks":false,"snowball":false,"spookySparkles":false,"shinySeed":false,"seafoam":false},"training":{"int":0,"per":0,"str":0,"con":0},"hp":50,"mp":34,"exp":64,"gp":26.471102126238144,"lvl":4,"class":"warrior","points":4,"str":0,"con":0,"int":0,"per":0},"notifications":[],"userV":327,"appVersion":"4.78.0"}
Set Cookie
```
ログが雑ですが、ちゃんと動きました。

#サーバー実行側をデーモン化
サーバー環境はCentOS7なので、systemctlでデーモンにします。
**[SystemdでNode.jsのサーバープログラムをお手軽にデーモン化する方法](https://cpoint-lab.co.jp/article/201809/systemd%E3%81%A7node-js%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%92%E3%81%8A%E6%89%8B%E8%BB%BD%E3%81%AB%E3%83%87%E3%83%BC%E3%83%A2%E3%83%B3/)**を参考に簡単にできました。
**[Run node.js service with systemd](https://www.axllent.org/docs/view/nodejs-service-with-systemd/)**も参考になりそうです。
下記ファイルを/etc/systemd/system/に作ります。

```:restredirector.service
[Unit]
Description=Rest redirector service by Node
After=syslog.target network.target

[Service]
Type=simple
ExecStart=/usr/bin/node /var/www/dev.kiyo.space/Study-Node/RestRelayService/main.js
WorkingDirectory=/var/www/dev.kiyo.space/Study-Node/RestRelayService/
KillMode=process
Restart=always
User=nginx
Group=nginx

[Install]
WantedBy=multi-user.target

```
あとは下記コマンドで登録と起動できます。標準出力はjournalctlに出ます。

```
# systemctl enable restredirector
# systemctl start restredirector
# journalctl -xe
...
 1月 01 23:15:29 konoe.studio node[12510]: Server started
```

次はTypescriptに挑戦します。
次回：**[年末年始Webアプリ開発自習の記録4: Typescript開発環境準備][Next]**

[Plan]:https://qiita.com/dmorita/items/7a85ee9c2de506c27502
[Prev]:https://qiita.com/dmorita/items/483b8fe40e9c4f9c950a
[Next]:https://qiita.com/dmorita/items/36bafecd1763328a6d59