WebSocketのクライアントとサーバーの処理を最もシンプルな実装で作成してみます。今回サーバー側ではws
というNPMパッケージを利用しています。
※ws: a Node.js WebSocket library
目次
サーバー側
ws
をrequire()
した後に、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:
の通信は暗号化されないため、情報が第三者に漏えいする可能性があります。