简体   繁体   中英

How to write a websocket client

I'm trying to unit test my websocket on node.js and want to mock out a websocket client. I could create a HTML file that just connects to my server but then I can't run a single test on the server.

How would I go about (using either http.Client or net.Stream ) to create a websocket client and have it interact with my server.

I'm targetting the (soon to be dead) draft 76 of the websocket spec.

The server side implementation I'm using is this

Since you already know that all current WebSocket version will be obsolete very soon and you're using a WebSocket server which supports the old 75 draft, it's fairly trivial to make one if you already have some server code lying around, so no need for the "security" header stuff in 76.

Disclaimer: This thing had only 5 minute of testing or so but it should work for the most part.

Epic wall of code follows

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;
    }
};

And here we set it up:

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) {

};

Feel free to ask questions in the comments.

PS: For info on how it works, read the spec :P

Take a look at the modules page on the wiki , it has some modules for websocket clients available in npm .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM