httpとwebsocketの兼用（統合）サーバーを作る

一例です。

# 前準備

## 前提・環境
- node.js: v10.16.0（async/awaitをサポートしている程度のバージョン）
- npm: v6.14.2（上のnode.jsとマッチするバージョンであればOK）
- windows 10 64bit

※ async/awaitは簡易化のために使用してるだけなので、必須ではないです。

## ディレクトリ構造
```
[ルートフォルダ]
|- public
  |- index.html
|- utils
  |- getLocalIp.js
|- server.js
|- …その他package.json等
```

## 依存モジュールのインストール
`npm i node-static ws`


# サーバー側
```./server.js
const { createServer } = require("http");
const { Server: FileServer } = require("node-static");
const { Server: WebSocketServer } = require("ws");
const getLocalIp = require("./utils/getLocalIp");

const PORT_NUM = 8080;

(async () => {
  // ローカルipアドレス取得
  const host = await getLocalIp().catch((err) => {
    throw err;
  });

  // httpサーバー立ち上げ
  const fileServer = new FileServer(`${__dirname}/public`);
  const server = createServer((request, response) => {
    request
      .addListener("end", function () {
        fileServer.serve(request, response);
      })
      .resume();
  });

  // websocketサーバー立ち上げ（兼用）
  const wsServer = new WebSocketServer({
    server: server,
  });

  // クライアントとの接続確立後
  wsServer.on("connection", (ws) => {
    ws.on("message", (message) => {
      console.log("Received: " + message);

      if (message === "hello") {
        ws.send("hello from server!");
      }
    });
  });

  // サーバーリッスン
  server.listen(PORT_NUM, host);
  server.on("listening", () => {
    // 準備完了でコンソールにURL表示
    const addressInfo = server.address();
    console.log(`http://${addressInfo.address}:${addressInfo.port}`);
  });
})();

```

```./utils/getLocalIp.js
/**
 * ローカルipアドレスを取得。非同期
 * @see https://stackoverflow.com/a/9542157
 */
module.exports = function () {
  return new Promise((resolve, reject) => {
    require("dns").lookup(require("os").hostname(), (
      error,
      address
    )=> {
      if (error) {
        reject(error);
        return;
      }
      resolve(address);
    });
  });
};
```


# クライアント側

```./public/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Websocket sample</title>
  </head>

  <h1>Websocket sample</h1>
  <body>
    <script>
      // URLがそのままwsとのコネクション用アドレスになる
      const { host, pathname, protocol } = window.location;
      const wsProtocol = protocol === "http:" ? "ws://" : "wss://";
      const address = wsProtocol + host + pathname;
      const socket = new window.WebSocket(address);

      // サーバーからのデータ
      socket.onmessage = (res)=> {
        if (typeof res.data === 'string') {
          alert(`Message from server: ${res.data}`)
        }
      };

      // コネクション確立後
      socket.onopen = () => {
        // メッセージ送ってみる
        socket.send('hello')
      }
    </script>
  </body>
</html>
```

# サーバー起動
`node server.js`で起動して、ターミナルに表示されるURLにアクセスするとalertでメッセージが表示される…はず。

## おまけ：サーバーを閉じたいとき
サーバーを閉じる際はwebsocketサーバーだけではなく、大元のhttpサーバーのほうも閉じる必要があるようです。

> Close the HTTP server if created internally, terminate all clients and call callback when done. If an external HTTP server is used via the server or noServer constructor options, it must be closed manually.
https://github.com/websockets/ws/blob/HEAD/doc/ws.md#serverclosecallback

```/server.js
wsServer.close();
server.close();
```

# 参考
- https://stackoverflow.com/questions/14273978/integrating-websockets-with-a-standard-http-server
- https://www.npmjs.com/package/websocket#server-example
- [VScode Live Serverのgithub](https://github.com/ritwickdey/vscode-live-server)（ホットリロードの仕組みにwebsocketを使用、インジェクトされるコードを参考にしました）