WebSokectライブラリの「ws」を使ってみた

node.jsのライブラリでWebSocketを実装しているwsを実際に使ってみた時のメモ。
実際には WebSocket チャットなサービス。
Google Chromeでしか試してない

構成
  • app.js : チャットアプリ
  • client.js : ターミナルからチャットするためのクライアント。ブラウザからしか使わない場合は必要ない
  • index.html : ブラウザでチャットするためのhtmlファイル
einaros/ws · GitHub 」を見る
ws のインストール
npm install ws

もしくは

npm install -g ws

やることは

  • http サーバーと WebSocket サーバーを同じ port から立ち上げる
  • クライアント(ブラウザ、その他)からのメッセージ送信を受け取ったら、接続している全てのクライアントにレスポンスメッセージを返す

ws では、socket.io や node-websocket-server のように 接続しているクライアント全てにメッセージを投げる broadcast が実装されていないので自前で実装する。具体的には、WebSocket が接続された時に、接続ソケットを格納する配列にpushして、メッセージを送信するときにループの中でメッセージを送信し、接続切れした時点で、そのソケットを配列から排除する という事を試している


app.js

var WebSocketServer = require('ws').Server
  , fs              = require('fs')
  , http            = require('http')
  , template        = 'index.html'
  , server          = httpServer(function (req, res) {
                          index(template, req, res);
                    })
  , wSServer        = new WebSocketServer({ // この段階で port 指定すると、http サーバーは ws モジュール内部のものを使うようだ
                        "server" : server
                    ,   "path"   : '/websocket' // パス指定すると、このパスのみ生きるっぽい
                    })
  , port            = 8080
  , connects        = []
;

wSServer.on('connection', function (ws) {
    log("WebSocketServer connected");

    connects.push(ws); // 配列にソケットを格納
    broadcast('connected sockets: ' + connects.length);

    ws.on('message', function (message) {
        log('received -' + message);
        broadcast(message);
    });

    ws.on('close', function () {
        log('stopping client send "close"');

        // 接続切れのソケットを配列から除外
        connects = connects.filter(function (conn, i) {
            return (conn === ws) ? false : true;
        });

        broadcast('connected sockets: ' + connects.length);
    });
});

server.listen(port);
log('Server Start on port -' + port + '- ');

function broadcast (message) {
    connects.forEach(function (socket, i) {
        socket.send(message);
    });
}

function log (str) {
    console.log((new Date).toString() + ' "' + str + '"');
}

function httpServer (onRequest) {
    var _server = http.createServer();

    _server.on('request', function (req, res) {
        log('httpServer on request');
        if (typeof onRequest === 'function') onRequest(req, res);
    });

    _server.on('close', function () {
        log('httpServer closing');
    });

    return _server;
}

function index (template, req, res) {
    fs.stat(template, function (err, stats) {
        if (err) return _error(err);
        if (! stats.isFile()) return _error('not file');

        fs.readFile(template, 'utf-8', function (err, data) {
            if (err) return _error(err);

            res.writeHead(200, {'Content-Type' : 'text/html' });
            res.write(data);
            res.end();
            log('raed file and pirnt: ' + template);
        });
    });
}

function _error (res, err) {
    res.writeHead(500, {'Content-Type' : 'text/plain' });
    res.end(err);
    log(err);
}

client.js

var argvs     = process.argv
  , readline  = require('readline')
  , rl        = readline.createInterface(process.stdin, process.stdout, null)
  , WebSocket = require('ws')
  , ws        = new WebSocket(argvs[2] || 'ws://localhost:8080/websocket');


ws.on('open', function () {
    console.log('connected');
    ws.send('WebSocket connected', { mask : true});
});
ws.on('close', function () {
    console.log('WebSocket disconected');
});
ws.on('message', function (data, flags) {
    console.log('< ' + data);
});


rl.on('line', function (line) {
    ws.send(line, {mask : true });

    rl.setPrompt('> ', 2);
    rl.prompt();
})
rl.on('close', function () {
    console.log('hava a good day!');
    process.exit(0);
});

rl.setPrompt('> ', 2);
rl.prompt();

index.html

<!doctype html>
<head>
<style>
.log {
    color            : #000000;
    border-bottom    : 1px solid #888888;
    background-color : #ffffff;
}
</style>
<script>
window.addEventListener('load', function () {
    var ws = new WebSocket('ws://localhost:8080/websocket')
      , domResult = document.getElementById('result')
      , domSend   = document.getElementById('send')
      , domClose  = document.getElementById('close')
      , textField = document.getElementById('textField')
      , response  = []
    ;

    function puts (str) {
        response.unshift( [ '<div class="log">', str, '</div>' ].join('') );
        domResult.innerHTML = response.join('\n');
    }
    ws.onopen = function () {
        puts('open');
    };
    ws.onclose = function (e) {
        if (e) return puts(e);
        puts('close');
    };
    ws.onmessage = function (e) {
        puts(e.data);
    };
    ws.onerror = function () {
        puts('ON_ERROR');
    };
    document.getElementById('close').addEventListener('click', function () {
        ws.close();
    });
    document.getElementById('sendF').addEventListener('submit', function () {
        if (textField.value) {
            ws.send(textField.value);
            textField.value = '';
            textField.focus();
        }
    });
});
</script>
</head>
<body>
  <form id="sendF" action="javascript:void(0);">
    <input type="text" id="textField" />
    <button id="send" type="submit">send</button><br />
    <button id="close" type="button">close</button>
  </form>
  <div id="result"></div>
</body>

その他

  • アプリ立ち上げは「node app.js」
  • index.html はローカルで開いても通信できる