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 '';
}
Related
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');
My application only needs socket.io to send data from the server to the client. To prevent Denial Of Service attacks, I want to disconnect the client if i tries to emit data. Is this possible?
I've looked at some stackoverflow questions:
force client disconnect from server with socket.io and nodejs
How to protect against distributed denial-of-service attacks in Node.js with Socket.io?
But I've not been able to find a working solution.
There is a option to cache all events (from here Socket.io Client: respond to all events with one handler?).
Then on any event you will just disconnect client on server side.
var socket = io.connect();
var globalEvent = "*";
socket.$emit = function (name) {
if(!this.$events) return false;
for(var i=0;i<2;++i){
if(i==0 && name==globalEvent) continue;
var args = Array.prototype.slice.call(arguments, 1-i);
var handler = this.$events[i==0?name:globalEvent];
if(!handler) handler = [];
if ('function' == typeof handler) handler.apply(this, args);
else if (io.util.isArray(handler)) {
var listeners = handler.slice();
for (var i=0, l=listeners.length; i<l; i++)
listeners[i].apply(this, args);
} else return false;
}
return true;
};
socket.on(globalEvent,function(event){
//Force disconnect
socket.disconnect();
});
This probably isn't very helpful, but the best I've heard of doing is Comet streams. It's an older method, and a lot of people don't like it (myself included), but it's an option for one-way server to client updates.
Essentially, on the client side you have an iframe that connects to the server, and the server sends back a response in the form of a multipart response, occasionally sending back script tags with bits of stuff to execute. So, a trivial (and probably broken) example would be this:
<!--index.html-->
<html>
<body>
<iframe src="/comet/status"></iframe>
</body>
</html>
And then the server code...
// server code (I like Node.JS)
app.get('/comet/status', function (req, res) {
// A function that does a lot of work,
// and occasionally calls a callback with progress
doWork(function (progress) {
res.write('<script>console.log("Progress: " + progress);</script>');
});
res.end();
});
Like I said, this is a pretty incomplete example, but it's a way to accomplish what you're looking for, even if in an older way. Instead of console logging, you'd probably update an element that displays progress.
I create a server with Node.js:
var net = require('net');
var PORT = 8181;
var server = net.createServer(
function(socket) {
console.log(this.address());
socket.on('data', function(data) {
var msg = data.toString().replace(/\n$/, '');
console.log('got: ' + msg);
});
process.stdin.on('readable',
function() {
var chunk = process.stdin.read();
if (chunk !== null) {
socket.write(chunk);
}
}
)
socket.write('heyyo\n');
}
)
Now, when multiple connections are coming in, this server sends out the typed in line only to the first connection.
I have two questions:
what is a standard way to handle this, i.e. to store the incoming sockets into an array?
exactly what happens that causes the readable event not to reach the other connections' callback function?
I would highly recommend using a library like socket.io. It makes handling connect/disconnect as well as placing sockets in rooms very simple. Additionally you can get the full list of available rooms and connected sockets through the adapter class it offers. A functional example is available in the docs.
on a basic node http server like this:
var http = require('http');
var server = http.createServer(function(req, res){
console.log("number of concurr. connections: " + this.getConnections());
//var pendingConnections = ???
});
server.maxConnections = 500;
server.listen(4000);
if i send 500 requests at one time to the server, the number of concurr. connections is arround 350. with the hard limit set to 500 (net.server.backlog too), i want to know, how to access the number of pending connections (max. 150 in this example) when ever a new request starts.
so i think i have to access the underlying socket listening on port 4000 to get this info, but until now i was not able to get it.
EDIT
looking at node-http there is an event called connection, so i think the roundtrip of an request is as follows:
client connects to server socket --> 3-way-handshake, socket lasts in state CONNECTED (or ESTABLISHED ?!) then in node event connection is emitted.
the node http server accepts this pending connection an starts processing the request by emitting request
so the number of connections has to be at least as big as the number of requests, but with following example i could not confirm this:
var http = require('http');
var activeRequets = 0;
var activeConnections = 0;
var server = http.createServer(function(req, res){
activeRequests++;
res.send("foo");
});
server.on('connection', function (socket) {
socket.setKeepAlive(false);
activeConnections++;
});
setInterval(function(){
console.log("activeConns: " + activeConnections + " activeRequests: " + activeRequests);
activeRequests = 0;
activeConnections = 0;
}, 500);
server.maxConnections = 1024;
server.listen(4000, '127.0.0.1');
even if i stress the server with 1000 concurr connections and adding one delay in the response, activeRequests is mostly as high as activeConnections. even worse, the activeRequests are often higher then activeconnections, how could this be?
IIRC You can just count how many connections that have a status of SYN_RECV for that particular IP and port that you're listening on. Whether you use a child process to execute netstat and grep (or similar utilities) for that information, or write a binding to get this information using the *nix C API, is up to you.
I am currently developing a chat system using Node.js. My question is, how do I handle multiple chat sessions with Node.js. That's a bit vague so I'll explain...
So for example two people might engage in a chat session. Node.js checks every couple of seconds for new messages. This is simple enough as I can get this to work fine using my code below.
My issue is when another two people at the same time are in another chat session.
At present I am sending two query strings called "chat_id" and "check_date" so it can check for new chat messages after a certain date and time.
Problem is, the "chat_id" and "check_date" are overwritten every time a new chat is started because the server is being used by both chat rooms.
Am I making sense?
My code below:
var chat_id, check_date;
var sys = require("sys"),
http = require("http"),
url = require("url"),
path = require("path"),
fs = require("fs"),
events = require("events");
// Conntect to database
var Client = require('mysql').Client;
var client = new Client();
client.host = 'localhost';
client.port = 3306;
client.database = '****';
client.user = '****';
client.password = '****';
client.connect(function(error, results) {
if(error) {
console.log('Connection Error: ' + error.message);
return;
}
console.log('Connected to MySQL');
});
// Check for chat updates
function getChatUpdates() {
if (typeof(check_date) == 'undefined')
return;
client.query('SELECT name, message FROM chat WHERE chat_id = "' + chat_id + '" AND date_entered > "' + check_date + '"',
function selectCb(error, results, fields) {
if (error) {
console.log('GetData Error: ' + error.message);
client.end();
return;
}
if (results.length > 0) {
for(var i = 0;i<results.length;i++) {
console.log('Name: ' + results[i]['name']);
console.log('Message: ' + results[i]['message']);
}
}
});
}
// Set interval to check for chat updates every 2 seconds
setInterval(getChatUpdates, 2000);
http.createServer(function(request, response) {
// Get the query strings
var uri = url.parse(request.url, true);
chat_id = uri.query.chat_id;
check_date = uri.query.check_date;
console.log('Chat ID: ' + chat_id);
console.log('Last Check: ' + check_date);
// we'll do something here later.
}).listen(8080);
sys.puts("Server running at http://localhost:8080/");
It seems like you are having a PHP background and when programming in node.js you should have a completely different mindset. You should not poll(setInterval(getChatUpdates, 2000);
) for messages, but push(callbacks/events) messages to users on event. I advice you to have a look at socket.io and use a in-memory database like redis(instead of mysql) to store your messages. When you code like this your site will perform much better and is going to be real-time(more). Also I would advice you to use express as your framework to develop websites instead of using raw http module. I don't know if you already know about npm, but you should use that to manage your dependencies.
var chat_id, check_date;
Are global variables. You are going to be overriding the chat_id on every connection (in createServer. You probably need some sort of session storage (to associate a user and their chat) eg. an Array, Redis, etc.
It looks like you trying to use MySQL as message queue. RabbitMQ?