Nodejs: websocket routing based on URL without port proxying - node.js

I am trying to do a game in html5 with serverside logic in node.js and that uses raw websockets (not Socket.IO, I need binary data). I wish to have multiple "rooms", thus multiple websocket servers, all having separate URLs. Currently, I only found a way to have each websocket server attached to a specific port, and then proxy the upgrade requests(not entirely sure how it works) to the right port based on the url.
It works on my computer. The problem is that when I try to submit it to a PaaS provider (AppFog), the code fails because they don't permit opening any ports other than the provided http port.
Here is a pretty cleared up version of my code:
//start web server (basic static express server) on 8080
// ...
//start game server and listen to port 9000
// I use the ws module for websockets
// I plan to have a couple of these "game servers"
// ...
//open the proxy server.
var httpProxy= require('http-proxy');
var webProxyServer = httpProxy.createServer(function (req, res, proxy){
// I need http requests to be redirected to the "game servers"
if(req.url.substring(0, "/room1".length) === "/room1") // if starts with "/room1"
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 9000
});
else
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 8080
});
}
webProxyServer.on('upgrade', function (req, socket, head) {
//redirecting logic goes here
if(req.url=="/room1/"){
webProxyServer.proxy.proxyWebSocketRequest(req, socket, head, {
host: 'localhost',
port: 9000
})
}
});
webProxyServer.listen(8000); //the "outside port".
My question: is it somehow possible to open websocket servers without listening to any specific ports, and to manually attach sockets to them so I don't need to open any ports other than the basic http port? I know Socket.IO somehow does it. Maybe there is a way to listen to the upgrade event of a http server and pass the socket to the right websocket server?
I am pretty new to server-side stuff, so extra info here and there would be welcome.

Unfortunately, Appfog does not support websockets.
Feature Roadmap page - bottom of page: shows websockets as something coming soon (i.e. they don't support it).

Related

Socket.IO with HTTPS over apache present?

I have a domain name.
I have a Raspberry Pi as a web-server.
I've edited domain's A record to point it to my server's IP.
Via letsencrypt I got myself a certificate and now website works on https protocol (keeping http on for debug purposes)
I'm working on a messenger app that uses socket.io but using apache+php for low level stuff
So basically apache listens to 80 and 443 and nodejs listens to 3000
Obviously if I visit my site over http - everything works fine and both server and client register connections.
If I visit it over https - Chrome throws net::ERR_CONNECTION_CLOSED error (in console when trying to connect to socket.io over port 3000. Site itself loads normally).
Client:
var socket = new io(window.location.host+":3000", { secure: true });
socket.on("connect", function() {
console.log('success')
});
Server:
const io = require("socket.io");
const server = io.listen(3000);
console.log("Server started");
server.on("connection", function (socket) {
console.log("+USER");
socket.emit("hello", "Connected");
});
I really don't want to use express or anything else for that matter to keep everything as small as possible, especially since I already have a web-server running.
How to properly set it up so users could connect to my socket.io server on port 3000 when they visit the site via https protocol?
Update:
From what it seems I think it's a CORS-thing type of a problem. User visiting website over https is trying to connect to an unsecured port (this case 3000) even though it's the same domain? I'd think that would be a no-no for a lot if not all browsers.
A solution comes to mind to just move the whole thing from apache to a nodejs server module and assign manually port 3000 as a secure one via https module but I've no idea how to do it, and I'd really want to keep my apache as a web-server because at least I'm more familiar with it than anything else.
Well I ended up creating a separate https server that I assume socket.io listens to (?)
Good thing I still have my apache as a main server. I partially answered my question using this post
https://serverfault.com/questions/745248/socket-io-combined-with-apache-ssl-server
Server
const fs = require("fs");
const https = require("https");
var options = {
key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem')
};
var server = https.createServer(options);
server.listen(3000);
var io = require('socket.io').listen(server);
console.log("Server started");
io.on("connection", function (socket) {
console.log("+USER");
socket.emit("hello", "Connected");
socket.on("disconnect", () => {
console.log("-USER");
})
});
Client
var s = new io("https://example.com:3000", { secure: true } );
Hope this is the right way to do it

What is best practice node.js recommended port in production environment?

I have created a chat app in node.js using port 4000. Everything works just fine, but when I rolled it out in production, I found that many corporate networks block outgoing port 4000. I considered using other ports that would be more likely to be open on a corporate network, but then found this list of ports blocked by chrome browser:
https://superuser.com/questions/188058/which-ports-are-considered-unsafe-by-chrome
Using ports such as 995 would result in a chrome error of "ERR_UNSAFE_PORT"
So it appears that the only ports allowed are 80 and 443 for a node.js server? What is the recommended best practice for choosing a port for your node.js application in a production environment?
My webserver is already using ports 80 and 443 for typical apache web serving. Do I need to create a dedicated server just for node.js?
I am using the following code to initiate the connection from the browser to the node.js server:
var socket = io.connect('https://duplex.example.com:4000');
and here is the code on the server side:
const https = require('https');
const fs = require('fs');
var express = require('express')
, bodyParser = require('body-parser');
var socket = require('socket.io');
var adminid = '';
var clientlist = new Array();
var port = 4000;
const options = {
cert: fs.readFileSync('./fullchain.pem'),
key: fs.readFileSync('./privkey.pem')
};
var app = express();
var server = https.createServer(options, app).listen(port, function(){
console.log("Express server listening on port " + port);
});
443 and 80 are the main ports for https and HTTP traffic respectively.
other ports can be used for WebSockets, but that doesn't sound like your use case.
What I have done in the past is use a reverse proxy, to discriminate on the incoming URL, and map the ports internally on my machine without the client needing to know.
NGINX is usually the easiest bet for this if you are on any sort of linux distro.
here is a blog about how to setup reverse proxy for a node app using nginx.
http://thejonarnold.com/configure-sails-js-with-subdomains-on-ubuntu/
the article references sailsjs, but there is nothing framework specific about the techique.
Most people don't expose their Node.js server directly to the internet but use Apache or Nginx as a frontend proxy.
Have your server bind to localhost only (or use firewall rules to only allow incoming 80 and 443.
server.listen('localhost', 4000)
Configure your reverse proxy. I'm using Caddy:
example.com {
root /var/www/example.com
# et cetera
}
duplex.example.com {
proxy / localhost:4000 {
websocket
}
}
When proxying websocket, you need to ensure the Connection and Upgrade headers aren't lost, which I've done with Caddy's shortcut here.
You could also use the same domain as the main site and only proxy a certain path.
Have the client socket.io connect to wss://duplex.example.com (on port 443). (I'm not familiar with socket.io to say why it uses an HTTPS URL instead of WSS, but I'll assume you have that working.)

Socket.io-based app running through node proxy server disconnecting all sockets whenever one disconnects

I made a basic chat app using node.js, express and socket.io. It's not too different from the tutorial chat app for socket.io, it simply emits events between connected clients. When I ran it on port 3001 on my server, it worked fine.
Then I made a proxy server app using node-http-proxy which listens on port 80 and redirects traffic based on the requested url to various independent node apps I have running on different ports. Pretty straightforward. But something is breaking. Whenever anyone disconnects, every single socket dis- and re-connects. This is bad for my chat app, which has connection-based events. The client consoles all show:
WebSocket connection to 'ws://[some socket info]' failed: Connection closed before receiving a handshake response
Here's what I think are the important parts of my code.
proxy-server.js
var http = require('http');
var httpProxy = require('http-proxy');
//create proxy template object with websockets enabled
var proxy = httpProxy.createProxyServer({ws: true});
//check the header on request and return the appropriate port to proxy to
function sites (req) {
//webapps get their own dedicated port
if (req == 'mychatwebsite.com') {return 'http://localhost:3001';}
else if (req == 'someothersite.com') {return 'http://localhost:3002';}
//static sites are handled by a vhost server on port 3000
else {return 'http://localhost:3000';}
}
//create node server on port 80 and proxy to ports accordingly
http.createServer(function (req, res) {
proxy.web(req, res, { target: sites(req.headers.host) });
}).listen(80);
chat-app.js
/*
...other modules
*/
var express = require("express");
var app = exports.app = express(); //I probably don't need "exports.app" anymore
var http = require("http").Server(app);
var io = require("socket.io")(http);
io.on("connection", function (socket) {
/*
...fun socket.on and io.emit stuff
*/
socket.on("disconnect", function () {
//say bye
});
});
http.listen(3001, function () {
console.log("listening on port 3001");
});
Now from what I've read on socket.io's site, I might need to use something to carry the socket traffic through my proxy server. I thought that node-http-proxy did that for me with the {ws: true} option as it states in their docs, but apparently it doesn't work like I thought it would. socket.io mentions three different things:
sticky session based on node's built in cluster module
socket.io-redis, which allows separate socket.io instances to talk to each other
socket.io-emitter, which allows socket.io to talk to non-socket.io processes
I have exactly no idea what any of this means or does. I am accidentally coding way above my skill level here, and I have no idea which of these tools will solve my problem (if any) or even what the cause of my problem really is.
Obligatory apology: I'm new to node.js, so please forgive me.
Also obligatory: I know other apps like nginx can solve a lot of my issues, but my goal is to learn and understand how to use this set of tools before I go picking up new ones. And, the less apps I use, the better.
I think your intuition about needing to "carry the socket traffic through" the proxy server is right on. To establish a websocket, the client makes an HTTP request with a special Upgrade header, signalling the server to switch protocols (RFC 6455). In node, http.Server instances emit an upgrade event when this happens and if the event is not handled, the connection is immediately closed.
You need to listen for the upgrade event on your http server and handle it:
var proxy = httpProxy.createProxyServer({ws: true})
var http = http.createServer(/* snip */).listen(80)
// handle upgrade events by proxying websockets
// something like this
http.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head, {target:sites(req.headers.host)})
})
See the node docs on the upgrade event and the node-http-proxy docs for more.

NodeJS listening on HTTP and HTTPS for one single domain name

I use express and a server cloud on AWS (Amazon Web Server) and a DNS "mydomain.com".
Question: how can I avoid my users to have to writing in the Browser-URL: http://mydomain.com:4000 and https://mydomain.com:3000
This is my code:
sudo node app.js
var app = express();
var server = http.createServer(app).listen(4000, function() {
console.log('Express HTTP server listening on port ' + app.get('port'));
});
var server = https.createServer(credentials, app).listen(3000, function() {
console.log('Express HTTPS server listening on port 3000');
});
// redirect all http requests to https
app.use(function(req, res, next) {
if(!req.secure) {
return res.redirect(['https://mydomain.com', req.url].join(''));
}
next();
});
I want my user to be able to write my domain name using http and https with no port numbers. I already have a SSL certificate and everything is working fine, but I haven't been able remove the port-numbers and use both: https and http.
Any idea? please :)
I use MEAN stack (Mongo, Express, Angular, )
The only way to do that is to use the default ports for the protocols. That is, Port 80 for HTTP and Port 443 for HTTPS.
If you don't use the default protocol ports then the only way for the browser (or whatever client the users are using) to determine which port to connect to is for the user to specify it in the URL.
Edit - To address your comment above about different server objects
In the code in your question you create an HTTP server and then use the variable server to hold a reference to the object. You then create an HTTPS server and assign it to the same variable. If you use the server variable later in your code then you'll be dealing with the HTTPS server object, but will have no way to reference the HTTP server object.
To fix this, just use two different variables to hold the object references.
var httpServer = http.createServer ....
var httpsServer = https.createServer ....

Serving socket.io through SSL

I'd like to implement ssl in all of my projects. One of them is using socket.io.
My code to create the socket:
var server = require('http');
var app = server.createServer(function(request, response) {
response.end();
});
var io = require('socket.io').listen(app);
app.listen(8000);
I can't bind node.js / socket.io to ssl port cause it's already in use by my webserver (cherokee). Therefore i can't serve the client using https.
Any ideas how to solve this problem?
You can only bind one application to a port. Since your web server is already bound to port 443, you have two choices:
Run the web server like you are now and proxy the node.js stuff running on port 8000:
http://www.cherokee-project.com/doc/modules_handlers_proxy.html
Run node.js on that port, change the port of the web server and proxy the web server using node.js: https://github.com/nodejitsu/node-http-proxy

Resources