Using node http-proxy to proxy websocket connections - node.js

I have an application that uses websockets via socket.io. For my application I would like to use a separate HTTP server for serving the static content and JavaScript for my application. Therefore, I need to put a proxy in place.
I am using node-http-proxy. As a starting point I have my websockets app running on port 8081. I am using the following code to re-direct socket.io communications to this standalone server, while using express to serve the static content:
var http = require('http'),
httpProxy = require('http-proxy'),
express = require('express');
// create a server
var app = express();
var proxy = httpProxy.createProxyServer({ ws: true });
// proxy HTTP GET / POST
app.get('/socket.io/*', function(req, res) {
console.log("proxying GET request", req.url);
proxy.web(req, res, { target: 'http://localhost:8081'});
});
app.post('/socket.io/*', function(req, res) {
console.log("proxying POST request", req.url);
proxy.web(req, res, { target: 'http://localhost:8081'});
});
// Proxy websockets
app.on('upgrade', function (req, socket, head) {
console.log("proxying upgrade request", req.url);
proxy.ws(req, socket, head);
});
// serve static content
app.use('/', express.static(__dirname + "/public"));
app.listen(8080);
The above application works just fine, however, I can see that socket.io is no longer using websockets, it is instead falling back to XHR polling.
I can confirm that by looking at the logs from the proxy code:
proxying GET request /socket.io/1/?t=1391781619101
proxying GET request /socket.io/1/xhr-polling/f-VVzPcV-7_IKJJtl6VN?t=13917816294
proxying POST request /socket.io/1/xhr-polling/f-VVzPcV-7_IKJJtl6VN?t=1391781629
proxying GET request /socket.io/1/xhr-polling/f-VVzPcV-7_IKJJtl6VN?t=13917816294
proxying GET request /socket.io/1/xhr-polling/f-VVzPcV-7_IKJJtl6VN?t=13917816294
Does anyone know how to proxy the web sockets communication? All the examples from node-http-proxy assume that you want to proxy all traffic, rather than proxying some and serving others.

Just stumbled upon your question, and I see that it is still not answered. Well, in case you are still looking for the solution...
The problem in your code is that app.listen(8080) is just syntactic sugar for
require('http').createServer(app).listen(8080)
while app itself is just a handler function, not an instance of httpServer (I personally believe that this feature should be removed from Express to avoid confusion).
Thus, your app.on('upgrade') is actually never used. You should instead write
var server = require('http').createServer(app);
server.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head);
});
server.listen(8080);
Hope, that helps.

Do you need both servers? If not you could use the same server for static files and to listen for socket connections:
// make the http server
var express = require('express'),
app = express(), server = require('http').createServer(app),
io;
// serve static content
server.use('/', express.static(__dirname + '/public'));
server.listen(8080);
// listen for socket connections
io = require('socket.io').listen(server);
// socket stuff here

Related

Can't upgrade to websockets using https via http-proxy in Nodejs

I am using http-proxy to proxy my requests. I am using https not http. The first request is a login request and works fine. The second request is to connect to socket.io which doesn't works even doesn't show any error. I have backend server which listens at port 3000 and handles both requests. Where is something wrong with in my code? The socket.io connection doesn't get established, even no error or anything is printed in the console to know root cause of the problem. I think it doesn't even gets upgraded. What do I do to make it work?
var app = require('express')();
const https = require('https');
const fs = require('fs');
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({ws:true});
var server=https.createServer(credentials, app).listen(8082)
app.use('/login', function (req, res) {
console.log("Request Url "+req.url)
proxy.web(req, res,{target:'example.com:3000'})
})
app.use('/socket.io',function(req,socket,head){
console.log("Request Url "+req.url)
server.on('upgrade', function(req, socket, head) {
socket.on('error', (error) => {
console.error(error);
});
proxy.ws(req, socket, head, {target:'example.com:3000'})
});
})
What I have tried
ws:true, secure:true options while proxying requests. Error eventlistener doesn't print any errors. Any help would be appreciated.

Socket.io client requests fail when frontend and backend run on different ports

I want to use Socket.io with a React frontend and NodeJS & Express backend but have both running on different ports for development (Fontend: 3000; Backend: 8080).
When the Socket.io-Client has loaded my frontend executes var socket = io('http://localhost:8080'); and then automatically makes a GET request to http://localhost:8080/socket.io/?EIO=4&transport=polling&t=NSlH7of. That request should normally return something like 0{"sid":"XXX","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000} but the Chrome Dev Tools say that the status is (failed) net::ERR_FAILED. There also is no response available in the Dev Tools.
However when I run the GET request in my HTTP-Client it returns exactly what I expect it to return.
That error looks like it's caused by the Socket.io-Client but I get no error whatsover besides the failed GET request. When I run everything on one port (Frontend served with webpack by the backend) the request goes through as expected.
I would appreciate any help! Thanks!
server.js
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
//Serve static react app
app.use(express.static('dist'));
app.use('/app', express.static('dist'));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + '../../../' + '/dist/index.html'));
});
io.on('connection', (socket) => {
console.log('Connected')
socket.on('join-game', (gameId, userId) => {
console.log(gameId, userId);
})
})
http.listen(process.env.PORT || 8080, () => {
console.log(`Listening on port ${process.env.PORT || 8080}!`);
});
I finally resolved the error!
The problem was that the request was blocked by CORS of Chrome.
I changed the line const io = require('socket.io')(http) to const io = require('socket.io')(http, { cors: {}});.
Now everything is working as it should.
If you want to hit port 8080 for your socket.io connection, you must set up a server for that port in your nodejs program (your back end). Code running in browsers (front-end code in React parlance) like your code
var socket = io('http://localhost:8080')
can only originate connection requests. And, if no server is listening for those requests, you get the failure status that your devtools showed you.
By the way, you'll be wise to make your nodejs server program use just one port. With just one port, it's much simpler to deploy to a production server.

Putting socket.io behind a reverse proxy?

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/"
})

Correct way to listen for HTTP and HTTPS in NodeJS

I would like to serve both HTTP and HTTPS in my NodeJS-app. It's for an internal applcaition where some visitors cannot support HTTPS.
Is this the simple correct way, or should it be 2 independent NodeJS app's?
http.createServer(app).listen(80, function () {
console.log('My insecure site');
});
https.createServer(options, app).listen(443, function () {
console.log('My sdecure site');
});
I don't think there is a better way. You can do a little more optimisation as both HTTP and HTTPS server do the same thing. Create a function called register that will configure middleware and routes. Then just invoke it for both HTTP and HTTPS.
var register = function (app) {
// config middleware
app.configure({
});
// config routes
app.get(...);
};
var http = express.createServer();
register(http);
http.listen(80);
var https = express.createServer();
register(https);
https.listen(443);

Meaning of socket.io listen a express server?

I am reading socket.io's how to, this follow code I can not understand :
Server (app.js)
var app = require('express')()
, server = require('http').createServer(app)
, io = require('socket.io').listen(server);
server.listen(80);
what is the meaning of io = require('socket.io').listen(server);, Is it just use same configurations with socket.io and express?
The listen function takes as argument an http event handler such as the ones you get from http.Server (it can also accept a port, in which case the listen functions creates the http server).
The http.createServer function creates an http Server from a request listener. And that's what's an express application : a request listener, as can be seen here :
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
...
return app;
}
Of course you don't need express to use socket.io, you may simply pass to listen a port or any instance of http.Server.

Resources