Putting socket.io behind a reverse proxy? - node.js

I recently decided to learn socket.io, to make something real-time. I wrote something up, following the Get Started page on the site, and tested it locally until I got it working properly.
I uploaded it to my server using the same process as anything else. I ran it on port 8002, and added it to my reverse proxy (using http-proxy-middleware) under /pong/*. I then proxied /socket.io/* to port 8002 before it worked. However after inspection with Firefox I noticed that socket.io was only using polling as a transport method and not websockets, and after some further thought I decided that sending /socket.io/* to 8002 is not going to be good when using socket.io on other projects in the future.
So I ask, how do I get multiple socket.io programs running behind a reverse proxy, using websockets as a for transport?
proxy.js
const express = require("express")
const fs = require('fs');
const http = require('http');
const https = require('https');
const proxy = require('http-proxy-middleware');
const privateKey = fs.readFileSync('/etc/[path-to- letsencrypt]/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/[path-to-letsencrypt]/cert.pem', 'utf8');
const ca = fs.readFileSync('/[path-to-letsencrypt]/chain.pem', 'utf8');
var credentials = {key: privateKey, cert: certificate, ca: ca};
var app = express();
app.use(function (req, res, next) {
console.log(req.url)
next()
})
app.use("/pong/*", proxy({ target: "http://localhost:8002", pathRewrite: {"^/pong": ""}, ws:true, changeOrigin: true }))
app.use("/pnw/war/*", proxy({ target: "http://localhost:8000" }))
app.use("/pnw/nation/*", proxy({ target: "http://localhost:8001" }))
app.use(express.static("./static"))
https.createServer(credentials, app).listen(443);
// Redirect all HTTP traffic to HTTPS
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
}).listen(80);
pong.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http, {
path: "/pong/"
});
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
http.listen(8002, function(){
console.log('listening on *:8002');
});
index.html
<script src="/pong/socket.io.js"></script>
<script>
var socket = io({
// transports: ['websocket'], upgrade: false, (using for testing)
path:"/pong"
})
// ...
</script>
What I have currently comes from following the answer to this question:
Setting up multiple socket.io/node.js apps on an Apache server?
However in the firefox console I get a warning which reads:
Loading failed for the <script> with source “https://curlip.xyz/pong/socket.io.js”, followed by an error io is not defined. In the network tab getting socket.io.js is showing a 404.
So what I believe is happening is that because express is capturing the requests for /, socket.io cannot (for some reason) server socket.io.js. However when I changed / to /index.html and loaded that there was no change.

So I did some more research and came upon a solution. I opened the port 8002 on my EC2 so that I could poke around looking for socket.io.js.
Essentially what I found is socket.io.js was located at /pong/pong/socket.io.js because I set path in pong.js to "pong", which, in hindsight make sense, the proxy adds one "pong", while socket.io itself is capturing "/pong".
Knowing this I removed the path option in pong.js, so that socket.io.js can be found at /pong/socket.io/socket.io.js. I then made the client point to this by changing the script tag and path option in index.html.
pong.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
http.listen(8002, function(){
console.log('listening on *:8002');
});
index.html
<script src="/pong/socket.io/socket.io.js"></script>
var socket = io({
path:"/pong/socket.io/"
})

Related

How can I adjust my NodeJs server code to responf to HTTPS requests?

I have a small nodejs server which is working without a problem. Now I am trying to make use of "HTTPS" for security reasons. I have the following code, but when I try to open the page in Firefox via link [http://192.168.2.22:8080/api/users], on the terminal I see DIRECTING >>> https://192.168.2.22:8080/api/users but in the browser, instead of the expected response, I encounter this error:
Secure Connection Failed An error occurred during a connection to
192.168.2.22:8080. SSL received a record that exceeded the maximum permissible length.
Error code: SSL_ERROR_RX_RECORD_TOO_LONG
// Modules /////////////////////////////////////////////////////////////////////
const db = require('./db.js');
// Packages ////////////////////////////////////////////////////////////////////
const colors = require('colors');
const express = require('express');
const app = express();
const fileUpload = require('express-fileupload');
const fs = require('fs');
// Constant Variables //////////////////////////////////////////////////////////
const PORT_SERVER = 8080;
const HOST = '192.168.2.22';
////////////////////////////////////////////////////////////////////////////////
app.use(express.json({limit: '50mb'}));
// app.use(express.urlencoded({limit: '50mb'}));
// set up a route to redirect http to https
app.get('*', function(req, res) {
console.log('DIRECTING >>> https ://' + req.headers.host + req.url);
res.redirect('https://' + req.headers.host + req.url);
});
app.get('/api/users/', async (req, res) => {
console.log('CHECK POINT !!!');
let users = await db.db.get_users();
console.log("USERS : " + users);
res.send(users);
});
// have it listen on 8080
app.listen(PORT_SERVER, () => console.log(`Listen at ${PORT_SERVER}...`));
How can I resolve this? I could not find a solution that I can easily apply to my code, I am kind of a newbie for NodeJs.
Thanks in advance
You have not configured your server for SSL. Configure SSL using the https module like below. In this example, I have created two express one for Http and one for https as we can not run both http and non https on same port.
const express = require('express');
const http = require('http'),
const https = require('https')
const fs = require('fs')
const httpApp = express()
const app = express()
const httpsOptions = {
key: fs.readFileSync("server.key"),
cert: fs.readFileSync("server.crt")
};
httpApp.set('port',80);
httpApp.get("*", function (req, res, next) {
res.redirect("https://" + req.headers.host + "/" + req.path);
});
app.set('port', 443);
app.enable('trust proxy');
http.createServer(httpApp).listen(httpApp.get('port'), function() {
console.log('Express HTTP server listening on port ' + httpApp.get('port'));
});
https.createServer(httpsOptions, app).listen(app.get('port'), function() {
console.log('Express HTTPS server listening on port ' + app.get('port'));
});
The Best way to redirect from non-http to https is to use Nginx web server as a reverse proxy and define redirection rule in Nginx config file.
client--->nginx reverse proxy(with SSL and redirection rules)-->express server
try changing the port to 443, https runs on 443 by default!

error conexion vue-socket ERR_CONNECTION_REFUSED

I have not managed to connect my vue js to my nodejs server. The configuration is the following, server side:
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
app.use(express.static(__dirname + '/dist'));
app.set('port', process.env.PORT || 5050);
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + '/dist/index.html'));
});
server.listen(app.get('port'), () => console.log(`http://localhost:${app.get('port')}`));
on the client side:
Vue.use(new VueSocketio({
debug: true,
connection: 'http://localhost:5050'
}));
when I test in local everything works perfect, the socket opens the events are issued without failure, the problem lies when I upload a server, the constant error:
vue-socketio.js:1 GET http://localhost:5050/socket.io/?EIO=3&transport=polling&t=MUu_7m4 net::ERR_CONNECTION_REFUSED
try to put the ip of the server provider and still the problem, I have seen many issues regarding this but I can not get a clear solution, I appreciate the help they can give me, thanks!

Socket.io not working with https (Let's Encrypt)

I am using ASP.NET CORE 2.0 to build an e-commerce. The e-commerce has a chat built using nodejs and the package socket.io. The socket.io server is remote on the server. When I use the Socket.io client locally, running Visual Studio Debugger, to access the remote socket.io, all works fine.
The code is like this, note that I am not using https
var app2 = require('express')();
var http = require('http').Server(app);
var http2 = require('http').Server(app2);
var io = require('socket.io')(http);
http.listen(3009, function () {
console.log('listening on port 3009');
});
http2.listen(3011, function () {
console.log('listening on port 3011');
});
But when I publish my web site and get the html page along with the socket.io client served by Nginx/kestrel I got an error message saying that I was mixing something, I didn't pay attention to the error message because I remembered that I was using http on my server socket.io and clients. So I changed the socket.io server and clients but now I cannot connect.
My changes are like this:
var app2 = require('express')();
var http = require('https').Server(app);
var http2 = require('https').Server(app2);
var io = require('socket.io')(http);
http.listen(3009, function () {
console.log('listening on port 3009');
});
http2.listen(3011, function () {
console.log('listening on port 3011');
});
clients
myIo = io('https://www.example.com.br:3009', { secure: true, reconnect: true, rejectUnauthorized: false });
I used Let's encrypt to enable https connections, I am using Nginx as proxy for Kestrel, I am using ufw on Ubuntu 17.
I got this error yesterday. I couln't even sleep at night. But I got it working. I sent the certificates like this.
var app = require('express')();
var app2 = require('express')();
var fs = require('fs');
var options = {
key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/example.com/cert.pem')
};
var http = require('https').Server(options, app);
var io = require('socket.io')(http);
app.get('/', function (req, res) {
res.send('server is running');
});
app2.get('/', function (req, res) {
res.send('admin area');
});
I don't want anyone passing the frustration felt. Hope I can help somebody.
You need to add the transports type for your client and server
server
var io = require('socket.io')(http);
io.set('transports', ['websocket']);
client
myIo = io('https://www.example.com.br:3009', { transports:
['websocket'], upgrade: false }, { 'force new connection': true });

nodejs can't work with SSL

Im trying to run nodejs app to work with my php project. the problem is I think with SSL which is enabled in the server.
I have two files that I found in my root directory after SSL install: domain.com.csr and domain.com.key and I tried to combine them to connection while creating https server, but nothing worked for me.
so far I have this code:
var socket = require('socket.io');
var express = require('express');
var http = require('http');
var app = express();
var server = http.createServer(app);
var io = socket.listen(server);
app.get('/test', function(req, res) {
res.send('hello world');
console.log('visited test')
});
io.sockets.on('connection', function (client) {
console.log("New client !");
client.on('message', function (data) {
console.log('Message received ' + data.name + ":" + data.message);
io.sockets.emit('message', {name: data.name, message: data.message});
});
});
server.listen(8080, function () {
console.log('listen me on: 8080');
});
and it works well when I'm trying to visit http://ip:8080/test so it means that node server is working, but when I try to create socket connection on my view file var socket = io.connect('http://ip:8080'); it gives me error:
The page at 'https://www.domain.com/' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://ip:8080/socket.io/?EIO=3&transport=polling&t=1446818946199-0'. This request has been blocked; the content must be served over HTTPS.
so the problem is clear enough, but how to deal with it?
also I have tried this connection:
var socket = io.connect('https://www.domain.com:8080');
but the result is 404 GET Error. How to deal with it?
Update
now the part of code I should use, but don't know how to get cert of existing SSL in the server.
var socket = require('socket.io');
var express = require('express');
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('path/to/key.pem'), // dont have
cert: fs.readFileSync('path/to/cert.cert') // dont have
};
var app = express();
var server = https.createServer(options, app);
var io = socket.listen(server);
app.get('/test', function(req, res) {
res.send('hello world');
console.log('visited test')
});
io.sockets.on('connection', function (client) {
console.log("New client !");
client.on('message', function (data) {
console.log('Message received ' + data.name + ":" + data.message);
io.sockets.emit('message', {name: data.name, message: data.message});
});
});
server.listen(443, function () {
console.log('listen me on: 443');
});
I think you need to contact your certificate authority (the organization that issued your first ssl certificate) and get a copy of the certificate (the path/to/key.pem and path/to/cert.cert) or find the existing keys somewhere on your existing server.
If you're running apache, your configuration file will have a section with values for the paths of the .cert and .pem files labeled SSLCertificateFile and SSLCertificateKeyFile, then just update the paths in your node app to point to them. You also have to make sure that your SSL certificate meets the requirements (for example, needs to be Multi-domain if your node app runs on a different domain, or a Wildcard SSL certificate to run your node app on a subdomain).
The domain.com.csr and domain.com.key files you found are the private key and certificate request used to generate your initial SSL certificate and aren't going to do anything to enable SSL on your node app.

Intercept HTTP responses on an HTTPS server in Node.JS and Express

I'm just starting with node.js and express and I'm doing a simple HTTPS server. I've been working with nginx for some time and when I make an HTTP request to an HTTPS endpoint I get a "400 Bad Request" error. However, when using node.js the request never finishes.
How can I intercept an HTTP request in Express to be able to generate the "400 Bad Request" response?
This is my code:
var express = require('express');
var https = require('https');
var fs = require('fs');
var port = process.env.PORT || 8080;
var tls_options = {
key: fs.readFileSync('certs/server.key'),
cert: fs.readFileSync('certs/server.crt'),
ca: fs.readFileSync('certs/ca.crt'),
requestCert: true,
};
var app = express();
var router = express.Router();
router.get('/', function(req, res) {
res.json({ message: 'Checkpoint!!' });
});
app.use('/', router);
var secureServer = https.createServer(tls_options, app);
secureServer.listen(port);
console.log('Listening on port ' + port);
Until now the only thing I've been able to use is getting a 'connection' event every time a request arrives to the server:
secureServer.on('connection', function (stream) {
console.log('someone connected!');
});
Done. In fact, an HTTP request to an HTTPS socket ends after the default 120secs TLS handsahke timeout. This way I can end the request without waiting. I include the solution I used just for future references if anything needs the same functionality.
var secureServer = https.createServer(options, app);
secureServer.on('connection', function(socket) {
socket.on('data', function(data) {
var first_line = data.toString().split('\r\n')[0];
var pattern = /\bhttp\/1\.[01]$\b/i;
if (pattern.test(first_line)) {
var headers = {};
headers['Date'] = new Date().toUTCString();
headers['Connection'] = 'close';
var headers_string = '';
for (var name in headers) {
headers_string = headers_string + '\r\n' + name + ': ' + headers[name];
}
socket.end('HTTP/1.1 400 Bad Request' + headers_string);
}
});
There isn't a way of starting both HTTP and HTTPS servers on the same port. What most people do is either:
Start two servers (one HTTP and one HTTPS) on different ports, and redirect the HTTP traffic to HTTPS. Using Express it would mean the additional code:
// create two ports, one for HTTP and one for HTTPS
var port = process.env.PORT || 8080;
var httpsPort = 8081;
// redirect all HTTP requests to HTTPS
app.use(function(req, res, next) {
var hostname;
if (!req.secure) {
hostname = req.get("host").split(":")[0];
return res.redirect(["https://", hostname, ":", httpsPort, req.url].join(""));
}
next();
});
app.listen(port); // listen on HTTP
https.createServer(tls_options, app).listen(httpsPort); // listen on HTTPS
Or they use nginx or apache to handle outside connections (both HTTP and HTTPS) and redirect traffic to the Node server (which can then just run on HTTP).

Resources