Okay, I've spent over a week trying to figure this out to no avail, so if anyone has a clue, you are a hero. This isn't going to be an easy question to answer, unless I am being a dunce.
I am using node-http-proxy to proxy sticky sessions to 16 node.js workers running on different ports.
I use Socket.IO's Web Sockets to handle a bunch of different types of requests, and use traditional requests as well.
When I switched my server over to proxying via node-http-proxy, a new problem crept up in that sometimes, my Socket.IO session cannot establish a connection.
I literally can't stably reproduce it for the life of me, with the only way to turn it on being to throw a lot of traffic from multiple clients to the server.
If I reload the user's browser, it can then sometimes re-connect, and sometimes not.
Sticky Sessions
I have to proxy sticky sessions as my app authenticates on a per-worker basis, and so it routes a request based on its Connect.SID cookie (I am using connect/express).
Okay, some code
This is my proxy.js file that runs in node and routes to each of the workers:
var http = require('http');
var httpProxy = require('http-proxy');
// What ports the proxy is routing to.
var data = {
proxyPort: 8888,
currentPort: 8850,
portStart: 8850,
portEnd: 8865,
};
// Just gives the next port number.
nextPort = function() {
var next = data.currentPort++;
next = (next > data.portEnd) ? data.portStart : next;
data.currentPort = next;
return data.currentPort;
};
// A hash of Connect.SIDs for sticky sessions.
data.routes = {}
var svr = httpProxy.createServer(function (req, res, proxy) {
var port = false;
// parseCookies is just a little function
// that... parses cookies.
var cookies = parseCookies(req);
// If there is an SID passed from the browser.
if (cookies['connect.sid'] !== undefined) {
var ip = req.connection.remoteAddress;
if (data.routes[cookies['connect.sid']] !== undefined) {
// If there is already a route assigned to this SID,
// make that route's port the assigned port.
port = data.routes[cookies['connect.sid']].port;
} else {
// If there isn't a route for this SID,
// create the route object and log its
// assigned port.
port = data.currentPort;
data.routes[cookies['connect.sid']] = {
port: port,
}
nextPort();
}
} else {
// Otherwise assign a random port, it will/
// pick up a connect SID on the next go.
// This doesn't really happen.
port = nextPort();
}
// Now that we have the chosen port,
// proxy the request.
proxy.proxyRequest(req, res, {
host: '127.0.0.1',
port: port
});
}).listen(data.proxyPort);
// Now we handle WebSocket requests.
// Basically, I feed off of the above route
// logic and try to route my WebSocket to the
// same server regular requests are going to.
svr.on('upgrade', function (req, socket, head) {
var cookies = parseCookies(req);
var port = false;
// Make sure there is a Connect.SID,
if (cookies['connect.sid'] != undefined) {
// Make sure there is a route...
if (data.routes[cookies['connect.sid']] !== undefined) {
// Assign the appropriate port.
port = data.routes[cookies['connect.sid']].port;
} else {
// this has never, ever happened, i've been logging it.
}
} else {
// this has never, ever happened, i've been logging it.
};
if (port === false) {
// this has never happened...
};
// So now route the WebSocket to the same port
// as the regular requests are getting.
svr.proxy.proxyWebSocketRequest(req, socket, head, {
host: 'localhost',
port: port
});
});
Client Side / The Phenomena
Socket connects like so:
var socket = io.connect('http://whatever:8888');
After about 10 seconds on logging on, I get this error back on this listener, which doesn't help much.
socket.on('error', function (data) {
// this is what gets triggered. ->
// Firefox can't establish a connection to the server at ws://whatever:8888/socket.io/1/websocket/Nnx08nYaZkLY2N479KX0.
});
The Socket.IO GET request that the browser sends never comes back - it just hangs in pending, even after the error comes back, so it looks like a timeout error. The server never responds.
Server Side - A Worker
This is how a worker receives a socket request. Pretty simple. All workers have the same code, so you think one of them would get the request and acknowledge it...
app.sio.socketio.sockets.on('connection', function (socket) {
// works... some of the time! all of my workers run this
// exact same process.
});
Summary
That's a lot of data, and I doubt anyone is willing to confront it, but i'm totally stumped, don't know where to check next, log next, whatever, to solve it. I've tried everything I know to see what the problem is, to no avail.
UPDATE
Okay, I am fairly certain that the problem is in this statement on the node-http-proxy github homepage:
node-http-proxy is <= 0.8.x compatible, if you're looking for a >=
0.10 compatible version please check caronte
I am running Node.js v0.10.13, and the phenomena is exactly as some have commented in github issues on this subject: it just drops websocket connections randomly.
I've tried to implement caronte, the 'newer' fork, but it is not at all documented and I have tried my hardest to piece together their docs in a workable solution, but I can't get it forwarding websockets, my Socket.IO downgrades to polling.
Are there any other ideas on how to get this implemented and working? node-http-proxy has 8200 downloads yesterday! Sure someone is using a Node build from this year and proxying websockets....
What I am look for exactly
I want to accomplish a proxy server (preferrably Node) that proxies to multiple node.js workers, and which routes the requests via sticky sessions based on a browser cookie. This proxy would need to stably support traditional requests as well as web sockets.
Or...
I don't mind accomplishing the above via clustered node workers, if that works. My only real requirement is maintaining sticky sessions based on a cookie in the request header.
If there is a better way to accomplish the above than what I am trying, I am all for it.
In general I don't think node is not the most used option as a proxy server, I, for one use nginx as a frontend server for node and it's a really great combination. Here are some instructions to install and use the nginx sticky sessions module.
It's a lightweight frontend server with json like configuration, solid and very well tested.
nginx is also a lot faster if you want to serve static pages, css. It's ideal to configure your caching headers, redirect traffic to multiple servers depending on domain, sticky sessions, compress css and javascript, etc.
You could also consider a pure load balancing open source solution like HAProxy. In any case I don't believe node is the best tool for this, it's better to use it to implement your backend only and put something like nginx in front of it to handle the usual frontend server tasks.
I agree with hexacyanide. To me it would make the most sense to queue workers through a service like redis or some kind of Message Query system. Workers would be queued through Redis Pub/Sub functionality by web nodes(which are proxied). Workers would callback upon error, finish, or stream data in realtime with a 'data' event. Maybe check out the library kue. You could also roll your own similar library. RabbitMQ is another system for similar purpose.
I get using socket.io if you're already using that technology, but you need to use tools for their intended purpose. Redis or a MQ system would make the most sense, and pair great with websockets(socket.io) to create realtime, insightful applications.
Session Affinity(sticky sessions) is supported through Elastic LoadBalancer for aws, this supports webSockets. A PaaS provider(Modulus) does this exactly. Theres also satalite which provides sticky sessions for node-http-proxy, however I have no idea if it supports webSockets.
I've been looking into something very similar to this myself, with the intent of generating (and destroying) Node.js cluster nodes on the fly.
Disclaimer: I'd still not recommend doing this with Node; nginx is more stable for the sort of design architecture that you're looking for, or even more so, HAProxy (very mature, and easily supports sticky-session proxying). As #tsturzl indicates, there is satellite, but given the low volume of downloads, I'd tread carefully (at least in a production environment).
That said, since you appear to have everything already set up with Node, rebuilding and re-architecting may be more work than it's worth. Therefore, to install the caronte branch with NPM:
Remove your previous http-node-proxy Master installation with npm uninstall node-proxy and/or sudo npm -d uninstall node-proxy
Download the caronte branch .zip and extract it.
Run npm -g install /path/to/node-http-proxy-caronte
In my case, the install linkage was broken, so I had to run sudo npm link http-proxy
I've got it up and running using their basic proxy example -- whether or not this resolves your dropped sessions issue or not, only you will know.
Related
I would like that the node Web Socket server (command: node /root/server.js) will refuse connection if PARAM is not 1. Connection like this will be refused: wss://example.com/socket.io/?**PARAM**=**0**&EIO=3&transport=websocket
server.js looks like this(EasyRTC Framework):
https://github.com/open-easyrtc/open-easyrtc/blob/master/server_example/server_ssl.js
I finally realized it's actually not that simple (doing inside server.js if PARAM != 1 then exit). Anyone can confirm this complexity or offer a simple solution, without recommending complex authentication frameworks. Also can't see that EasyRTC Framework will offer something like this.
Can someone lead me in the right direction because I am lost.
Thank you
I crawled all the EasyRTC Group, there is an authentication system for node server.js web socket and it's very simple:
var onAuthenticate = function(socket, easyrtcid, appName, username, credential, easyrtcAuthMessage, next){
// do your checks
next(null); // Run next with a null if they are allowed in / or if not the socket is "disconnected" for them not for others.
};
easyrtc.events.on("authenticate", onAuthenticate);
Tested and works perfectly. More testing will follow.
I need to send data out from my google assistance app to a database. In order to do this, I've created a server that takes the data, packages it, and then sends it out. I have the hostname and port and it works in a normal javascript/node.js program but when I use it in my google assistant app nothing happens. I tried figuring out the problem and it looks like the code just isn't connecting. The code I'm using to send data to the server is as follows:
function sendData(app){
var net = require('net');
var message = {"test": 200};
var thisMessage = JSON.stringify(message);
var client = new net.Socket();
client.connect(<port>, '<hostname>', function() {
app.tell(JSON.stringify(client.address()));
console.log('Connected');
client.write(thisMessage);
});
client.on('data', function(data) {
console.log('Received: ' + data);
client.destroy();
});
client.on('close', function() {
console.log('Connection closed');
});
return 0;
}
(NOTE: Port and hostname left out for privacy purposes)
This completely skips over the app.tell, leading me to believe the connection is never made. I know it works asynchronously with the server, however, I don't understand why it isn't connecting whatsoever.
I have tried it both in simulation and on my smartphone with sandbox on and off. Is there a better way to connect? Note that the server I'm connecting to is python-based.
The problem is likely that you're running it on Cloud Functions for Firebase which has a limit on outbound connections under their free "Spark" plan. With this plan, you can only connect to other Google services. This is usually a good way to start understanding how to handle Action requests, but has limitations. To access endpoints outside of Google, you need to upgrade to either their "Flame" fixed price plan or "Blaze" pay-as-you-go plan.
You do not, however, need to run on Google's servers or need to use node.js. All you need is a public HTTPS server with a valid SSL cert. If you are familiar with JSON, you can use any programming language to handle the request and response. If you are familiar with node.js, you just need a node.js server that can create Express request and response objects.
My Project is built with Nodejs as proxy server to communicate with an external API.
The API send product updates via Redis (pub/sub); The Proxy server handle the message and send it to the client via SSE (Server Sent Events).
It is the first time for me using Redis and SSE and looking online for tutorials seems to be easy to implement and I did it.
On the Client side I just created an EventSource and as soon as I receive an update I do something with it:
// Client Side
var source = new EventSource('/redis'); // /redis is path to proxy server
source.addEventListener('items', handleItemsCallback, false);
source.addEventListener('users', handleUsersCallback, false);
source.addEventListener('customers', handleCustomersCallback, false);
// Function sample...
function handleItemsCallback (msg) {
// Do something with msg...
}
In the Proxy server I created a controller with routing to /redis to handle Redis messages:
exports.redisUpdates = function (req, res) {
// Redis Authentication
var redisURL = url.parse(process.env.REDISCLOUD_URL);
var client = redis.createClient(redisURL.port, redisURL.hostname, {ignore_subscribe_messages: false});
client.auth(redisURL.auth.split(":")[1]);
// let request last as long as possible
req.socket.setTimeout(0);
// Subscribe to channels
client.subscribe('items', 'users', 'customers');
// Handle messages
client.on('message', function (channel, message) {
res.write('retry: 5000\n');
res.write('event: ' + channel + '\n');
res.write('data: ' + message + '\n\n');
res.flush(); // If I do not add this it doesn't push updates to the client (?)
});
//send headers for event-stream connection
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
res.write('\n');
};
Using it locally in a development environment it works fine but using it in Production generate several different errors, the App is hosted on Heroku and the Heroku Metrics show several H18, H12, H27 Errors;
Sometimes the /redis call return status 503;
What I wish to understand is if I'm using those services correctly, why all tutorials do not mention res.flush() and I discovered it by myself to let it work the first time...
In all fairness, this question is not really answerable for a few reasons. I don't know which tutorials you are talking about since you didn't reference any in the question. I cannot speak on behalf of those who wrote the unreferenced tutorials. They could just be wrong, or maybe the architecture of what you are trying to accomplish differs in some small way. I also don't know what framework or optional middleware you are using in your project.
Now, with all of that said there are a few things I can share that may help you out.
Most tutorials you find out there are probably not going to open a connection and read from the stream indefinitely. When the process ends, the http response is closed with .end() or something similar. Since an HTTP response is a write stream, it follows the same rules as any other stream. You can find a lot of good info about streams here:
https://github.com/substack/stream-handbook
Something important to understand is that a stream can have a buffer and most http frameworks enable compression which causes buffers to be used. The code sample in the next link is a good example of what a framework would do for you behind the scenes (a minimal implementation of course)
https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/#what-we-ve-got-so-far
Since you want the output to continue being updated, you either have to wait until the output buffer size is reached or you have to call .flush().
If you ARE using express, check out this next Stack Overflow post related to compression middleware. I believe you'll have to have it disabled for your /redis route.
Node Express Content-Length
I hope that helped a little. Like I said, its kind of hard to answer this question. ;)
I have a socket server running with netty-socketio and a web app that connects to it using socket.io-client JS library.
The problem is that I'm losing a few connections (not all, let's say 20%).
For the lost connections: right after the connection is made by the client, the server logs client did not complete upgrade - closing transport and disconnects the client.
This happens on my production server (using nginx as proxy) and also on my local environment (connecting directly to the netty-socketio server). It's pretty much random and I cant identify a pattern on it. For example, if I continuously keep refreshing the client app on the browser (with a 5 seconds interval), at some point this error will happen, and for the subsequent tries it will work normal again (until it happens another time).
This is the error on the netty-socketio lib: https://github.com/mrniko/netty-socketio/blob/master/src/main/java/com/corundumstudio/socketio/transport/WebSocketTransport.java#L196
but I could not figure out why it happens randomly (some times at the first try)
Any thoughts on this are really appreciated.
Thanks
After some research and tests I found out that when using netty-socketio as server, you need to specify the transport method on the client side.
var socket = io('server-address', { transports: [ 'polling' ] });
// or
var socket = io('server-address', { transports: [ 'websocket' ] });
If you don't specify it, the connection will be established using polling as transport method and netty will automatically try to upgrade it to websocket. This is what was causing connection failures.
After specifying the transport method I had 0% connection failures so far.
I am new to Node.js and also pretty new to server communication.
I have tried to find previous answers, but they are often concerned about communication between server and client.
I have a different case, so need your considerate helps.
Let's assume a scenario that we have three systems, localhost (i.e., laptop) and two cloud servers. I want to code an js app in the localhost that will slice an array of data into two blocks and send them to the cloud servers (block #1 to the server #1 and block #2 to the server #2). Receiving them, two remote servers start to work at the same time. Then, they do the same computation and send their calculation results to each other if they have updated values.
In this scenario, I want to tackle bolded sentences. I believe using the module "socket.io" will be a proper approach to handle this (especially, remote start and communication) but do not have any clear idea in designing codes. In addition, understanding "socket.io" itself is a bit tricky. If you need further specification on the scenario, please comment.
Along with socket.io, check out a module named Faye (http://faye.jcoglan.com/node.html). I have been using it for a couple of years and really like it. Faye is a publish subscribe communication scheme which would allow you to extend your scenario to as many clients as you need. To install faye on your system, run the following command:
npm install -g faye
Now, here is your server code:
var faye = require('faye');
var Server = new faye.NodeAdapter({mount: ('/FayeServer'), timeout: 120});
//now fire the server up on the port 5555
Server.listen(5555);
//subscribe to channel DataChannel
var Subscription = Server.getClient().subscribe("DataChannel",
function(dataObject){ console.log(dataObject) },
function(status) {
console.log('Subscription Status: ' + status);
//send message with two numbers to any client listening to DataChannel
Server.getClient().publish('/DataChannel', {A:5,B:12});
});
Now, here is the client code:
var faye = require('faye');
//open client to server
var Client = new faye.Client('http://127.0.0.1:5555/FayeServer');
//now subscribe to the channel DataChannel
Client.subscribe('/DataChannel', function(dataObject)
{
Client.publish('/DataChannel', {C:(dataObject.A * dataObject.B)};
});
There is a lot more that can be done, but with this basic framework you can stand up server to N client programs that respond to messages from the server.
You will need to replace 127.0.0.1 with your specific URL and use port numbers and channel names more applicable to your specific application.