如何编写一个WebSocket客户端

9
我想对我的node.js websocket进行单元测试,并想要模拟一个websocket客户端。我可以创建一个连接到我的服务器的HTML文件,但这样我就无法在服务器上运行单个测试。
我应该如何(使用http.Clientnet.Stream)创建一个websocket客户端,并让它与我的服务器交互。
我针对websocket规范的第76版(即将死亡)进行了目标设置。
我正在使用服务器端实现。
2个回答

11

由于您已经知道所有当前的WebSocket版本很快都将过时,而且您正在使用支持旧版75草案的WebSocket服务器,所以如果您已经有一些服务器代码,制作新的WebSocket服务器就相当简单,无需76中的“安全”头部内容。

免责声明:这个东西只测试了大约5分钟左右,但大部分情况下应该可以工作。

接下来是一个大量的代码

var net = require('net');

function WebSocket(host, port, encoder, decoder) {
    this.encoder = encoder || function(data){return data.toString()};
    this.decoder = decoder || function(data){return data};
    this.socket = net.createConnection(port, host);

    this.connected = false;
    this.header = 0;
    this.bytesSend = 0;
    this.dataFrames = [];
    this.dataState = 0;

    var that = this;
    process.nextTick(function() {
        that.init(host, port);
    });
}


// Prototype -------------------------------------------------------------------
WebSocket.prototype = {
    onConnect: function() {console.log('connect');},
    onClose: function() {console.log('close');},
    onData: function(data) {console.log(data)},

    init: function(host, port) {
        var that = this;
        this.socket.addListener('connect', function() {
            var data ='GET / HTTP/1.1\r\n'
                  + 'Host: ' + host + ':' + port + '\r\n'
                  + 'Origin: websocket.node.js\r\n'
                  + 'Connection: Upgrade\r\n'
                  + 'Upgrade: WebSocket\r\n\r\n';

            that.socket.write(data, 'ascii');
            that.socket.flush();
        });
        this.socket.addListener('data', function(data) {that.read(data);});
        this.socket.addListener('end', function() {that.onClose();});
        this.socket.addListener('error', function(e) {console.log(e.message);that.close();});
    },

    send: function(data, encoded) {
        if (this.connected) {
            return this.write(encoded ? data : this.encoder(data));

        } else {
            return 0;
        }
    },

    close: function() {
        if (this.connected) {
            this.connected = false;
            this.write(null);
            this.socket.end();
            this.socket.destroy();
        }
    },

    read: function read(data) {
        for(var i = 0, l = data.length; i < l; i++) {
            var b = data[i];
            if (this.header < 4) {
                if ((this.header === 0 || this.header === 2) && b === 0x0d) {
                    this.header++;

                } else if ((this.header === 1 || this.header === 3) && b === 0x0a) {
                    this.header++;

                } else {
                    this.header = 0;
                }

                if (this.header === 4) {
                    this.connected = true;
                    this.onConnect();
                    this.header = 5;
                }

            } else {
                if (this.dataState === 0) {
                    this.dataState = b & 0x80 === 0x80 ? 2 : 1;

                // Low bit frame
                } else if (this.dataState === 1) {
                    if (b === 0xff) {
                        var buffer = new Buffer(this.dataFrames);
                        this.dataFrames = [];
                        this.dataState = 0;

                        if (!this.message(buffer.toString('utf8', 0, buffer.length))) {
                            this.send({error: 'Invalid Message.'});
                            this.close();
                            return;
                        }

                    } else {
                        this.dataFrames.push(b);
                    }

                // Unused high bit frames
                } else if (this.dataState === 2) {
                    if (b === 0x00) {
                        this.close();
                    }
                }
            }
        }
    },

    write: function(data) {
        var bytes = 0;
        if (!this.socket.writable) {
            return bytes;
        }

        try {
            this.socket.write('\x00', 'binary');
            if (typeof data === 'string') {
                this.socket.write(data, 'utf8');
                bytes += Buffer.byteLength(data);
            }
            this.socket.write('\xff', 'binary'); 
            this.socket.flush();
            bytes += 2;

        } catch(e) {}

        this.bytesSend += bytes;
        return bytes;
    },

    message: function(msg) {
        if (this.decoder) {
            try {
                msg = this.decoder(msg);

            } catch(e) {
                this.close();
                return false;
            }
        }
        this.onData(msg);
        return true;
    }
};

然后我们进行设置:

var bison = require('bison');

// automatically do some encoding/decoding magic
var test = new WebSocket('localhost', 28785, bison.encode, bison.decode);
test.onConnect = function() {

};

test.onData = function(data) {

};

欢迎在评论区提出问题。

附注:如需了解其工作原理,请阅读规范 :P


0

你能否不只是说“这是一个列表”,而是剪枝一下,为稳定/最近/经过充分测试的 WebSocket 客户端列出清单,并且这些客户端可以与 0.4.8 兼容,并实现更为新进的 06 协议。 - Raynos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接