简体   繁体   中英

Websocket closes on client message [JavaScript/Node.js]

I'm creating a simple Node.js WebSocket server, but I am running into a problem after the initial handshake.

In the beginning, I was only using chrome and the command line to monitor back and forth between a HTML5 Websocket and the Node.js server. It took a bit to implement the protocol, but I had just finished a very basic version of the server-side message decoding. I was having a hard time, however, because whenever I would call ws.send('some kind of message') , the websocket would close on the client side. Looking into the network tab of the DevTools, it looks like the message would send from the client, and get an immediate error response of (Opcode -1) , and would log this error in the console:

WebSocket connection to 'ws://localhost:4000/' failed: A server must not mask any frames that it sends to the client.

I've looked into what it all means, and I can't figure out why my code would throw it. I had tried rebuilding it, and also making a test message send after the confirmation, which worked. The only thing I had not tried was using a different browser, so I tried it today. And it worked as expected.

Below is all my relevant code.

Libraries, constants, and listens:

const hostname = 'localhost';
const webport = 8080;
const socketport = 4000;
const http = require('http');
const net = require('net');
const mysql = require('mysql');
const rlm = require('readline');
const crypt = require('crypto');

...

server.listen(webport,hostname);
socketServer.listen(socketport,hostname);

HTTP Server:

const server = http.createServer(
    function(req,res) {
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write("
<html>
    <head>
        <title>Test Title</title>
    </head>
    <body>
        <h1>Here's the thing</h1>
        <p>im baby</p>
    </body>
    <script>
        const ws = new WebSocket('ws://"+hostname+":"+socketport+"');
        ws.addEventListener('message',function(data){
            console.log(data.data)
        });
    </script>
</html>
        ");               // Reformatted for better reading
        res.end();
    });

Net Server:

var sockets = new Map();
var socketInfo = {};

 const socketDelimiters = {
    'Accept-Encoding':',',
    'Accept-Language':';',
    'Sec-WebSocket-Extensions':'; '
}

const socketServer = net.Server(function(s) {
    s.on('data',function(e) {
/*
 * If the socket is not registered, read first message as
 * the beginning to a handshake
 */
        if(sockets.get(s)==null) {
            var str = ""+e;
            var tempobj = str.split("\r\n");
            var newObj = {};
            for(var i in tempobj) {
                if(tempobj[i].length>0) {
                    var tempProperty = tempobj[i].split(': ');
                    if(tempProperty.length>1) {
                        if(socketDelimiters[tempProperty[0]]!=null){
                            tempProperty[1] = tempProperty[1].split(
                            socketDelimiters[tempProperty[0]]);
                        }
                        newObj[tempProperty[0]] = tempProperty[1];
                    } else {
                        newObj.header = tempProperty;
                    }
                }
            }
            var protocolReturn = "
                HTTP/1.1 101 Switching Protocols\r\n
                Upgrade: websocket\r\n
                Connection: Upgrade\r\n
                Sec-Websocket-Accept: "+createAcceptKey(newObj['Sec-WebSocket-Key'])
                +"\r\n\r\n";      //Reformatted for better reading
            s.write(protocolReturn);
            s.pipe(s);
            sockets.set(s,newObj['Sec-WebSocket-Key']);
            socketInfo[newObj['Sec-WebSocket-Key']] = {
                socket:s,
                isReading:false,
                message:null,
                mask:null,
                handshake: newObj
            };
            s.write(Buffer.from([0x81,0x04,0x74,0x65,0x73,0x74])); // 'test'
            s.pipe(s);
        } else {

/*
 *  If the socket is found and registered, decode the incoming message
 */
            var firstBytes = e.readUInt16BE(0);
            console.log(firstBytes);
            var length=((firstBytes & 0x007F)/0x0001);
            var FIN = ((firstBytes & 0x8000))!=0;
            var opcode = (firstBytes & 0x0F00)/0x0100;
            var mask = ((firstBytes & 0x0080)!=0);
            if(opcode!=8) {
                console.log("end: "+FIN);
                console.log("mask: "+mask);
                console.log("op code: "+opcode);
                console.log("length: "+length);
                var mask = [];
                for(var i=0; i<4; i++) {
                    var b = e.readUInt8(2+i);
                    mask.push(b);
                }
                var val=[];
                for(var i=0; i<length; i++) {
                    var b = e.readUInt8(6+i) ^ mask[i%4];
                    val.push(b);
                }
                var newVal = new Buffer.from(val);
                console.log(newVal.toString('utf8'));
            }
        }
    })

        // Handles error
    s.on('error',function(err) {
        console.log(err);
    })

        // Takes socket out of the socket list on close
    s.on('close',function(hasError) {
        if(hasError) {console.log("Please see error")}
        delete socketInfo[sockets.get(s)];
        sockets.delete(s);
    });
});

// Generates accept key from given key
function createAcceptKey(key) {
    var inKeyHash = crypt.createHash('sha1');
    inKeyHash.update(key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    return (inKeyHash.digest('base64'));
}

What all this should do ('<' means server to client, '>' means client to server)

> [handshake initiation]
< [handshake confirmation]
< test
> [anything the client sends through the console]

/*
 *All I do for the client to server bit at the end is go into the console,
 * and plug in something like this
 */
ws.send('blah blah blah')

This works perfectly fine in Firefox, but as explained above, in chrome, it throws an error, claiming that the server had sent a masked frame at the same instant the client sends a message to the server.

Is there a reason that chrome reads a masked frame and firefox does not?

UPDATE:

I have now tried to use this in a different browser (the OBS browser to be exact) and it throws the same error on the server's side that connecting with Chrome does (I've added an event listener to send a message on socket open on the client side). Would anyone know why it only works in Firefox?

Solved this two days ago, didn't realize I could post my own answer (still new to posting here, sorry!)

A lot of my understanding of Node.js sockets came from the net documentation. In this, there is an example of a server and client interaction. The pipe() command is used after writing on the server side, so I assumed that it was necessary in writing to a socket client.

It is not required, and in fact should not be used. The example is an echo server, so every message the client sends to the server will be relayed back to the client. This post is the one that helped me with this, but I am a bit mad, because I tried following that advice before, and it stopped working when I removed the pipe commands. If the definition of insanity is "Trying something again and expecting different results," then throw me in the loony bin.

TL,DR;

Writing to the socket was easier than I thought:

// Expected:
socket.write('blah blah blah');
socket.pipe(socket);

// Reality
socket.write('blah blah blah');

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