Why does my websocket handshake fail with unexpected response 200? - node.js

I have seen this question several times, but I feel that my use case is not quite addressed, so I'm posting it below:
I get this error when trying to connect to my websocket:
scripts.js:44 WebSocket connection to 'ws://localhost:3000/' failed: Error during WebSocket handshake: Unexpected response code: 200
There's one small change that makes it fail versus succeed. See below:
Server.js:
'use strict';
const express = require('express');
const WebSocket = require('ws');
const PORT = process.env.PORT || 3000;
// Failing:
const server = express().use(express.static('public'));
server.get('/', function (req, res) {
res.sendFile(path.join(__dirname + '/public/index.html'));
});
server.listen(PORT, () => {
console.log(`Example app listening on PORT ${PORT}!`)
});
///
// Working (no websocket connection issues):
///const server = express().use(express.static('public'))
// .listen(PORT, () => {
// console.log(`Example app listening on PORT ${PORT}!`)
// });
const wss = new WebSocket.Server({ server });
wss.on('connection', function connection(ws) {
console.log(ws);
ws.on('message', function incoming(message) {
console.log('received message: %s', message);
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
});
relevant: client code:
var HOST = location.origin.replace(/^http/, 'ws');
var socket = new WebSocket(HOST); //this is the line where the connection is attempted.
Running on localhost, ngrok, or deployed to heroku seems to make no difference, unless I missed something...
I have tried various things from other posts but to no avail. They mention changing networking configurations (doesn't seem relevant to my use), or various ways to configuring express js, but they don't help. It's really just that small change in the above code. Is the websocket hitting the GET route?

Related

Express and Websocket to run on the same port on the same file

I'm running two apps that sends real-time messages to each other using websocket and also generate a random link using express.js, now i hosted the server with both react apps to my vps host and want to make the websocket connection secure (wss://) but i realize i'll have to get the express server on the same port too, so the ssl/tsl works for both - so how do i do that?
Here is my full code, all on the same file:
const webSocketServerPort = 8000;
const webSocketServer = require('websocket').server;
const http = require('http');
const server = http.createServer(); server.listen(webSocketServerPort); console.log('Listening on port 8000');
const wsServer = new webSocketServer({ httpServer: server })
//GEERTOOOO
const express = require('express'); const cors = require('cors'); const fs = require('fs'); const app = express();
app.use(cors({ origin: '*' }));
app.get('/', (req, res) => { // Generate a random 6-character string const linkId = Math.random().toString(36).substr(2, 6);
// Save the link in the lex.json file fs.readFile('lex.json', (err, data) => { if (err) { console.error(err); res.status(500).send('Error generating link'); return; }
const links = JSON.parse(data);
links[linkId] = {
destination: 'http://localhost:4000/',
expires: Date.now() + 1000 * 60 * 5 // expires in 5 minutes
};
fs.writeFile('lex.json', JSON.stringify(links), (err) => {
if (err) {
console.error(err);
res.status(500).send('Error generating link');
return;
}
// Send the link back to the client
res.send(`http://localhost:3000/${linkId}`);
});
}); });
app.get('/:linkId', (req, res) => {
fs.readFile('lex.json', (err, data) => {
if (err) { console.error(err); res.status(500).send('Error retrieving link');
return;
}
const links = JSON.parse(data);
const link = links[req.params.linkId];
if (!link) {
res.status(404).send('Link not found');
return;
}
// Check if the link has expired
if (link.expires < Date.now()) {
res.status(410).send('Link has expired');
return;
}
// Redirect to the destination
res.redirect(link.destination);
}); });
app.listen(3000, () => { console.log('Server listening on port 3000'); });
//GEERTOOOO
const clients = {};
const getUniqueID = () => { const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
return s4() + s4() + '-' + s4(); }
wsServer.on('request', (request) => { var userID = getUniqueID();
const connection = request.accept(null, request.origin); clients[userID] = connection;
connection.on('message', (message) => {
if (message.type === 'utf8') {
for(var key in clients) {
if (clients[key] !== clients[userID]) {
clients[key].sendUTF(message.utf8Data);
console.log(`Sent Message to: ${clients[key]}`);
}
}
}
}) })
Note: the express server is on port 3000 and the websocket server runs on port 8000.
I,ve tried just changing the port to same thing but i get an error when trying to use the websocket server for messages.
THE PURPOSE OF ALL THIS IS JUST TO MAKE THE WEBSOCKET CONNECTION AND EXPRESS CONNECCTION SECURE SO MY APPS (with letsencrypt ssl) can connect to the servers
It is not possible to create two separate server instances, both listening on the same port. But, specifically for a webSocket, you can share one server instance between Express and the webSocket server code. This is possible because a webSocket connection always starts with an http request (thus it can be listened for using your Express http server. And, because these http requests that initiate a webSocket all contain identifying headers they can be separated out from the regular http requests for Express by looking at the headers. The webSocket server code already knows how to do that for you.
To do that, first capture the Express server instance:
const server = app.listen(3000, () => { console.log('Server listening on port 3000'); });
Then, use that server instance when you create your webSocket server.
const wsServer = new webSocketServer({ httpServer: server });
Then, remove this code because you don't want to create yet another http server instance for the webSocket server:
const server = http.createServer();
server.listen(webSocketServerPort);
console.log('Listening on port 8000');

How to send websocket message to client from the serverside

Hello I have two backends laravel and Nodejs
and There will be one frontend.
So if the front end requests something on laravel and laravel requests to node and Node has to send a message to the client through WebSocket.
How to do It?
Index.js
const app = require('express')();
const http = require('http');
const WebSocket = require('ws')
//initialize a simple http server
const server = http.createServer(app);
app.get('/', function(req, res) {
res.sendFile(__dirname + '/index.html');
});
let sendNotification;
//initialize the WebSocket server instance
const wss = new WebSocket.Server({ server });
let socketapi = require('./socketapi')
socketapi.start(wss)
//start our server
server.listen(process.env.PORT || 5555, () => {
console.log(`Server started on port ${server.address().port} :)`);
});
socketapi.js
module.exports ={
start: (wss) => {
wss.on('connection', (ws) => {
console.log('connected!!!');
console.log(socketIds)
//connection is up, let's add a simple simple event
// triggerMessage('data');
ws.id=uuidv4()
ws.on('message', (message) => {
console.log('received: %s', message);
// ws.send(`Hello, you sent -> ${message}`);
});
}
}
}
Now I want to use this socket in the controller file and send it as I get a request.
When I export and import socket it logs as undefined.
I have stored and ws in array with particular Id and that Id was associated with data in DB so I can use that Id to get ws and send through that ws on function calling

how to connect via WebSocket running on Ubuntu server

Im have hetzner server on which im trying to run WebSocket. Unfortunately I got stack so here is my code from test.js)
const https = require('https');
const fs = require('fs');
const ws = require('ws');
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
};
let server = http.createServer(options, (req, res) => {
console.log(req);
res.writeHead(200);
res.end();
});
server.addListener('upgrade', (req, res, head) => console.log('UPGRADE:', req.url));
server.on('error', (err) => console.error(err));
server.listen(8080, () => console.log('started on 8080'));
const wss = new ws.Server({ server, path: '/echo' });
wss.on('connection', (ws) => {
console.log('client connected');
ws.send('Hello');
ws.on('message', (data) => ws.send('Receive: ' + data));
});
wss.on('error', (e) => console.log(e));
* I got this code from some sources
After running the server I got message in console started on 8080. Nothing else... I tried to test it but I always got errors with no code. It looks like service where I tested it cannot find WebSocket. It is possible that problem actually in connecting to the serer.
Im not shure which path string should I use. I have hostname ubuntu-s**** and server IP 49.****. I did lots of attempts with 'wss://ubuntu-s***:8080/echo' and 'wss://49.***:8080/echo' but none of them gave any result
I still have no messages in console that cliend tried to connect :( Moreover I tried to run it on the local server (of course I removed SSL sertificates connecthion and changed server protocol to HTTP) and it works perfectly!!!
Thanks a lot for urs replies
UPD: message I got when trying to connect ws WebSocket connection to 'ws://49.***:8080/echo' failed:
First double check if the IP address is correct then change protocol to HTTP because you are using it on top of HTTP:
http://49.***:8080/echo

Socket.IO server.origins always returns '*' as the origin value (Nodejs)

I am trying to build a two way socket.io server/client connection. The server will remain behind one IP/domain and the client will behind a different IP. The point is to notify me when the server goes offline, in case of power outage or server failure. The issue I am having, is I am trying to secure the socket so not just anyone can connect to the socket. Socket.IO has a server.origins function that will return the origin of socket trying to connect. Their API documentation explains it like this.
io.origins((origin, callback) => {
if (origin !== 'https://foo.example.com') {
return callback('origin not allowed', false);
}
callback(null, true);
});
The issue I am having is whenever I connect to the socket.io server with socket.io-client the origin is always '*'.
Under potential drawbacks in there API is says:
"in some situations, when it is not possible to determine origin it may have value of *"
How do I get socket.io it see the IP where the socket connection request is coming from?
Once the connection is established I can use the socket information and see the IP where the socket lives, but the connection is already made. I am trying to stop rouge connections.
# Server
const express = require('express');
const app = express();
const chalk = require('chalk')
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const cors = require('cors');
const port = 4424;
app.use(cors());
io.origins((origin, callback) => {
console.log(origin);
if (origin !== '*') {
return callback('origin not allowed', false);
}
callback(null, true);
});
io.on('connection', (socket) => {
console.log('Client connected...');
socket.on('join', (data) => {
console.log(data);
socket.emit('messages', 'Hello from server');
});
})
server.listen(port, () => console.log(chalk.blue(`Express started on port ${port}!`)));
Client:
# Client
const io = require('socket.io-client');
const socket = io('https://"MY DOMAIN THAT THE SERVER IS BEHIND"', { reconnect: true })
socket.on('connect', (data) => {
console.log("Connection successful");
socket.emit('join', 'Hello World from client');
});
socket.on('connect_error', (error) => {
console.log("Connection error");
});
socket.on('disconnect', (timeout) => {
console.log("Connection disconnected");
})
socket.on('messages', (data) => {
console.log(data);
});
I have the server behind a NGINX server using SSL, and connected to the server with the client on a different IP and it goes through and creates the connection, but the Origin is always "*".
Actually I found out you can use middleware with Socket.io with the io.use() function. I just wrote a simple middleware that checks the incoming socket ip with a list of approved ips.
io.use((socket, next) => {
const ip = socket.handshake.headers['x-forwarded-for']
if (firewall(ip))
{
return next();
}
})
And firewall is a function that checks if the ip is in the array of approved ips.

HTTP connection gets prematurely terminated in nodejs app hosted on Amazon EC2

I have a REST api hosted on Amazon EC2, which is written with Nodejs (Express).
In a particular REST call, a reply of about 5MB is sent to the client. Before the client completely receives the reply, client prints following error message.
Premature end of Content-Length delimited message body
I added a connection listener in nodejs server like below to check what is going on the server.
var app = express();
var server = http.createServer(app);
var port = app.get('port');
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
server.on('connection', function (socket) {
log.debug('SOCKET OPENED' + JSON.stringify(socket.address()));
socket.setTimeout(300000); //5 minute timeout
socket.on('end', function () {
log.debug('SOCKET END: other end of the socket sends a FIN packet');
});
socket.on('timeout', function () {
log.warn('SOCKET TIMEOUT');
});
socket.on('error', function (error) {
log.warn('SOCKET ERROR: ' + JSON.stringify(error));
});
socket.on('close', function (had_error) {
log.debug('SOCKET CLOSED. IT WAS ERROR: ' + had_error);
});
});
I observed that SOCKET TIMEOUT gets logged in backend. In above code, I have increased the socket timeout to 5 minutes, but it doesn't seem to have any effect.
Earlier I had the REST API hosted in Google compute engine, and I didn't have this problem back then.
What could be the problem here?
Edit: Here is the code of REST API call.
I have following code in my app.js
require('./routes/index')(app);
Following is the index.js of routes directory.
var changeCase = require('change-case');
var express = require('express');
var routes = require('require-dir')();
module.exports = function (app) {
Object.keys(routes).forEach(function (routeName) {
var router = express.Router();
require('./' + routeName)(router);
app.use('/api/' + changeCase.paramCase(routeName), router);
});
};
As it can be seen, it loops through all the js files in the routes directory and registers the file name as the URL path in app.
Here is the code of this particular route for which I face this problem.
module.exports = function (router) {
router.get("/fetch", function (req, res, next) {
itemModel.fetch(req.user.clientId, function (error, items) {
if (error) {
res.status(500).json({error: error});
} else {
res.json(items); //items is a JSON array
}
});
});
}
Setting timeout for the HTTP server resolved the issue.
var server = http.createServer(app);
var port = app.get('port');
server.listen(port);
server.setTimeout(300000, function (socket) {
});

Resources