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');
Related
So i came across a problem.I am trying to send {id} to my rest API (node js) and in response, I get data on the socket.
Problem:
For first 5-6 time it works perfectly fine and display Id and send data back to socket.But after 6 time it does not get ID.
I tried this https://github.com/socketio/socket.io/issues/1145
and https://github.com/socketio/socket.io/issues/1145 but didn't solve the problem.
On re compiling the server it shows previous {ids} which i enter after 6 time.it like after 5-6 time it is storing id in some form of cache.
Here is my API route.
//this route only get {id} 5-6 times .After 5-6 times it does not display receing {id}.
const express = require("express");
var closeFlag = false;
const PORT = process.env.SERVER_PORT; //|| 3000;
const app = express();
var count = 1;
http = require('http');
http.globalAgent.maxSockets = 100;
http.Agent.maxSockets = 100;
const serverTCP = http.createServer(app)
// const tcpsock = require("socket.io")(serverTCP)
const tcpsock = require('socket.io')(serverTCP, {
cors: {
origin: '*',
}
, perMessageDeflate: false
});
app.post("/getchanneldata", (req, res) => {
console.log("count : "+count)
count++;// for debugging purpose
closeFlag = false;
var message = (req.body.val).toString()
console.log("message : "+message);
chanId = message;
client = dgram.createSocket({ type: 'udp4', reuseAddr: true });
client.on('listening', () => {
const address = client.address();
});
client.on('message', function (message1, remote) {
var arr = message1.toString().split(',');
}
});
client.send(message, 0, message.length, UDP_PORT, UDP_HOST, function (err, bytes) {
if (err) throw err;
console.log(message);
console.log('UDP client message sent to ' + UDP_HOST + ':' + UDP_PORT);
// message="";
});
client.on('disconnect', (msg) => {
client.Diconnected()
client.log(client.client)
})
}
);
There are multiple issues here.
In your app.post() handler, you don't send any response to the incoming http request. That means that when the browser (or any client) sends a POST to your server, the client sits there waiting for a response, but that response never comes.
Meanwhile, the browser has a limit for how many requests it will send simultaneously to the same host (I think Chrome's limit is coincidentally 6). Once you hit that limit, the browser queues the request and and waits for one of the previous connections to return its response before sending another one. Eventually (after a long time), those connections will time out, but that takes awhile.
So, the first thing to fix is to send a response in your app.post() handler. Even if you just do res.send("ok");. That will allow the 7th and 8th and so on requests to be immediately sent to your server. Every incoming http request should have a response sent back to it, even if you have nothing to send, just do a res.end(). Otherwise, the http connection is left hanging, consuming resources and waiting to eventually time out.
On a separate note, your app.post() handler contains this:
client = dgram.createSocket({ type: 'udp4', reuseAddr: true });
This has a couple issues. First, you never declare the variable client so it becomes an implicit global (which is really bad in a server). That means successive calls to the app.post() handler will overwrite that variable.
Second, it is not clear from the included code when, if ever, you close that udp4 socket. It does not appear that the server itself ever closes it.
Third, you're recreating the same UDP socket on every single POST to /getchanneldata. Is that really the right design? If your server receives 20 of these requests, it will open up 20 separate UDP connections.
I have been trying to implement an HTTP server which receives POST requests from a particular computer. The HTTP server will then extract the message body received and send the body's data to another client, using web sockets.
The code for the HTTP server is the following:
var http=require('http');
var server = http.createServer(function(request, response){
var msgbody='';
if(request.method == "POST"){
request.on('data', function(data){
msgbody=data;
//upon receiving POST request send msgbody to the client using websockets
});
}
}).listen(80);
Could you please provide me with some insight regarding how the web sockets part can be correctly implemented alongside the running HTTP server? The HTTP server and the web socket server need to run on the same port and IP address.
Thank you
Yes, you can totally do that. For starters, one confusing thing is that the websocket initial request won't come to your data event. It will come to the upgrade event. See node docs for more details.
In your instance, your other server will need to contact this server first with a websocket upgrade request, and that connection will be established. Then when you receive the POST request, you'll need to resend that data across the already-existing websocket request from another server.
Your best bet is absolutely to use an existing library such as ws. You can use this library to attach to an existing http server. See example here. (Example says express, but if you look I believe the ws library is actually attaching to a regular node http server)
If you're curious about exactly how it works, or if your heart is dead set on writing your own websocket server: it is certainly possible. Here's a bare-bones example of what you'd need to do to receive the frames of data from the client. I never actually got around to making the send frames, but this MDN page explains the entire process in detail.
server.on('upgrade', handleWS);
function handleWS(request, socket, buf) {
var key = getHeader(request, 'Sec-WebSocket-Key');
var magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
var shasum = crypto.createHash('sha1');
shasum.update(key + magic);
var akey = shasum.digest('base64');
var resp = ['HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept: ' + akey, '', ''].join('\r\n');
console.log(key, resp);
socket.write(resp);
var inbuff = '';
socket.on('data', function (buf) {
var fin = buf.readUInt8(0) >> 7;
var opcode = buf.readUInt8(0) & 15; //0=cont, 1=text, 2=binary
var mask = buf.readUInt8(1) >> 7, bmask;
var len = buf.readUInt8(1) & 127;
var i = 2;
if (len === 126) { len = buf.readUInt16BE(i); i += 2; }
else if (len === 127) {
len = (buf.readUInt32BE(i) << 32) + buf.readUInt32BE(6);
i += 8;
}
if (mask) { bmask = buf.slice(i, i + 4); i += 4; }
data = buf.slice(i, i + len);
if (mask) for (var j = 0; j < data.length; j++)
data[j] = data[j] ^ bmask[j % 4];
if (opcode === 1) data = data.toString('utf8');
// todo: handle fragmentation
console.log(fin, opcode, mask, len, data);
})
}
function getHeader(req, key) {
var keyl = key.toLowerCase()
for (var k in req.headers) if (k.toLowerCase() === keyl) return req.headers[k];
return '';
}
Because people can open many tabs and use many browsers, I have some troubles of determining when user close all tabs of each browser.
If all tabs are closed, the user is no longer connected so I assume you want to know on the server if he is completely disconnected?
You should hold a list of sockets against a user identifier (login name or similar) on the server, when a new tab is opened it will have a new socket connection so add it to the list.
When a socket connection is closed, remove it from the socket collection for that user.
When the user's last socket connection is closed, you know that the user has completely disconnected.
EDIT with example
something like this (untested and hastily written!)
'use strict';
var userConnections = [];
io.on('connection', function (socket) {
var username = socket.request.user.username;
var existingUser = userConnections.find(function(userConnection){
return userConnection.username === username;
})
if (!existingUser){
existingUser = {
username: username,
sockets: []
}
}
existingUser.sockets.push(socket);
socket.on('disconnect', function () {
var socketIndex = existingUser.indexOf(socket);
existingUser.sockets.splice(socketIndex, 1);
if (existingUser.sockets.length === 0){
//user has completely disconnected
}
});
});
EDIT - after clarification (see comments)
OP has indicated he wishes to know when all connections for a particular browser instance have disconnected.
Since you cannot access any system information about the browser process from javascript I don't see any way of achieving this.
It is possible to detect the browser type (Chrome/IE/Edge etc) on the client and send this information on socket connection. You could then store your socket information referencing this information. However I don't think this is what the OP wants.
Here is my solution, it depends on #Banners's one.
"socket.cookies" stores the browser's cookies
Please let me now if I was missing something.
'use strict';
var userConnections = {};
io.on('connection', function (socket) {
var username = socket.request.user.username;
var visit_id = (socket.cookies.vid) ? socket.cookies.vid : random_unique_id();
//set cookie here
setCookie('vid', visit_id, expire);
if (!userConnections[visit_id])
userConnections[visit_id] = [];
userConnections[visit_id].push(socket.id);
socket.on('disconnect', function () {
var vid = socket.cookies.vid;
if (userConnections[vid]) {
var index = userConnections[vid].indexOf(socket.id);
if (index != -1)
userConnections[vid].splice(index, 1);
if (userConnections[vid].length === 0) {
delete userConnections[vid];
//All tabs have been closed
}
}
});
});
I'm trying to implement a TCP proxy in Node JS. I only have some experience with Javascript so I met a lot of problems along the way. I've done a lot of searching for this one but had no luck.
The problem occurs when browser sends a CONNECT request for HTTPS. My proxy will parse the host name and port, and then create a new socket that connects to the server. If all these steps went well, I will start forwarding message.
Part of my code looks like this:
var net = require('net');
var server = net.createServer(function(clientSock) {
clientSock.on('data', function(clientData) {
var host = // get from data
var port = // get from data
if (data is a CONNECT request) {
// Create a new socket to server
var serverSock = new net.Socket();
serverSock.connect(port, host, function() {
serverSock.write(clientData);
clientSock.write('HTTP/1.1 200 OK\r\n');
}
serverSock.on('data', function(serverData) {
clientSock.write(serverData);
}
}
}
}
Since the CONNECT request needs both client socket and server socket open until one side closes the connection, the code above doesn't have this behavior. Every time I receive some data from client, I will create a new socket to server and the old one is closed.
Is there a way to store the server socket as a global variable so that the data event handler can reuse it? Or is there any other way to solve this?
Thanks a lot!!!!
You can just move the variable up to a higher scope so it survives across multiple events and then you can test to see if its value is already there:
var net = require('net');
var server = net.createServer(function(clientSock) {
var serverSock;
clientSock.on('data', function(clientData) {
var host = // get from data
var port = // get from data
if (data is a CONNECT request) {
// Create a new socket to server
if (!serverSock) {
serverSock = new net.Socket();
serverSock.connect(port, host, function() {
serverSock.write(clientData);
clientSock.write('HTTP/1.1 200 OK\r\n');
}
serverSock.on('data', function(serverData) {
clientSock.write(serverData);
}
} else {
serverSock.write(clientData);
}
}
}
}
I want to create extension that use Pusher service(http://pusher.com/) to send messages to the client, and I am getting the following error message:
Port error: Could not establish connection. Receiving end does not exist.
When I try the same code on simple html page it works.
Pusher.log = function(message) {
if (window.console && window.console.log) window.console.log(message);
};
// Flash fallback logging - don't include this in production
WEB_SOCKET_DEBUG = true;
var pusher = new Pusher('key');
var channel = pusher.subscribe('channel');
channel.bind('event', function(data) {
alert("test");
});
Do I need to change anything in order to make it run?
thx.