taketiyo.log

Web Engineering 🛠 & Body Building 💪

【Node.js】WebSocketことはじめ【Javascript】

Programming

  / / / /

WebSocketのクライアントとサーバーの処理を最もシンプルな実装で作成してみます。今回サーバー側ではwsというNPMパッケージを利用しています。
ws: a Node.js WebSocket library

 

目次

 

サーバー側

wsrequire()した後に、new WebSocket.Server()するだけです。シンプルですね。

const WebSocket  = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {

  console.log('クライアントとの接続を確立しました');

  ws.on('message', (message) => {
    console.log(`クライアントよりメッセージを受信しました: ${message}`);
  });
});

これをserver.jsなどとして保存し、サーバー側でnode server.jsとコマンドを実行し起動すれば完了です。

 

クライアント側(ブラウザ)側

サーバーへのテストメッセージ送信用にボタン要素を配置しています。
クリックすると、サーバーへメッセージが送信されます。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Hello WebSocket!!</title>
</head>
<body>

  <button type="button" class="btn-send-message">メッセージ送信</button>

  <script>
    // WebSocketコネクションを確立
    let socket = new WebSocket('ws://localhost:8080');

    socket.addEventListener('error', () => {
      console.log('サーバーへの接続に失敗しました');
    });

    socket.addEventListener('open', () => {
      console.log('サーバーに接続しました');
    });

    socket.addEventListener('message', (e) => {
      console.log(`サーバーよりメッセージを受信しました: ${e.data}`);
    });

    // ボタンをクリックした際のイベント
    document.querySelector('.btn-send-message').addEventListener('click', () => {
      // サーバー側にメッセージを送信します
      socket.send('クライアントからのメッセージです');
    });
  </script>

</body>
</html>

最低限上記の実装があれば、サーバー、クライアント間にて通信を行うことが可能です。

 

応用 – upgrade要求を手動でハンドリングする

WebSocketは標準ではそれ自体にユーザ認証、アクセス認可の仕組みを備えていません。
つまり安全なWebSocketコネクションを確立するためには、upgrade要求を受け入れる前にOriginヘッダーの検証やユーザー認証などの処理を挟む必要があります。
 
サーバー側を下記のように拡張します。

const http      = require('http');
const WebSocket = require('ws');

const server = http.createServer();
const wss    = new WebSocket.Server({ noServer: true });

wss.on('connection', (ws) => {

  console.log('クライアントとの接続を確立しました');

  ws.on('message', (message) => {
    console.log(`クライアントよりメッセージを受信しました: ${message}`);
  });
});

/**
 * 接続元として許可するOriginのリスト
 */
const allowedOrigins = [
  'http://localhost:8081',
  'https://socket.example.com',
];

/**
 * upgrade要求が来た際の処理
 */
server.on('upgrade', function (request, socket, head) {

  // リクエスト元のOriginを取得
  const origin = request.headers.origin;

  // Originの検証
  if (!allowedOrigins.includes(origin)) {
    // 許可リストに存在しないOriginからの接続要求だった場合ソケットを破棄
    return socket.destroy();
  }

  /** その他必要に応じた認証処理を行う */

  // 全ての認証処理を通過した場合、upgrade要求を受け入れる
  wss.handleUpgrade(request, socket, head, (ws) => {
    // コネクションを確立させる
    wss.emit('connection', ws, request);
  });
});

/**
 * 8080番ポートにてサーバーのリッスンを開始
 */
server.listen(8080);

 

異なるのはnew WebSocket.Server()にてWebSocketサーバーを初期化する際、オプションとして{ noServer: true }を渡している点と、http.createServer()にて時前でサーバーを作成し、server.on('upgrade', Fn)にてupgrade要求を手動で処理している点となります。
より安全にWebSocketを運用するためには、upgrade要求の手動ハンドリングが必須となります。

 

おわりに

プロダクションにて運用する際は、Originヘッダーの検証に加えてユーザー認証処理も行い、更にクライアントからのデータは必ず検証するといった対応が必要になります。また可能であればws:ではなくwss:プロトコルを使用しましょう。ws:の通信は暗号化されないため、情報が第三者に漏えいする可能性があります。