Https server with websockets on node.js - node.js

I'm trying to create simple https server with websockets support. I had some functional code for https server and websockets, but both standalone. Don't know how to combine it. I prefer to use free to use libraries (MIT,..)
I want to be able to serve http request and also websockets.
Https example with upgrade, but don't know how to handle upgraded websocket connection as my second example bellow this.
connection. process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var https = require('https');
var websocket = require('websocket');
var fs = require('fs');
var options = {
key:fs.readFileSync('./cert/server.key'),
cert:fs.readFileSync('./cert/server.crt')
};
var server = https.createServer(
options
, function(req,res) {
console.log('req');
res.writeHeader(200, { 'Content-Type': 'text/plain' });
res.write('test');
res.end();
}
);
server.on('upgrade', (req, socket, head) => {
console.log('upgrade ');
socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
'Upgrade: WebSocket\r\n' +
'Connection: Upgrade\r\n' +
'\r\n');
socket.pipe(socket);
});
server.listen(8080, '127.0.0.1', () => {
// make a request
const options = {
port: 8080,
hostname: '127.0.0.1',
headers: {
'Connection': 'Upgrade',
'Upgrade': 'websocket'
}
};
const req = https.request(options);
req.end();
req.on('upgrade', (res, socket, upgradeHead) => {
console.log('got upgraded!');
*** so what to do here ***
/*
socket.end();
process.exit(0);
*/
});
});
This is functional websockets but without possibility to handle simple http(s) requests.
var https = require('https');
var ws = require('websocket').server;
var fs = require('fs');
var options = {
key:fs.readFileSync('./cert/server.key'),
cert:fs.readFileSync('./cert/server.crt')
};
var server = https.createServer(
options
, function(req,res) {
res.writeHeader(200);
res.end();
}
);
server.listen(8080);
var wss = new ws({httpServer:server});
var connectionNumber=0;
console.log('start ');
wss.on('request',function(req){
req.on('requestAccepted',function(conn){
conn.on('message',function(msg){
conn.send('test');
});
conn.on('close',function(msg){
});
});
req.accept(null,req.origin);
});
I was looking for functional example of combined solution but haven't any luck.

You can to have wss-connection trouble with self-signed certificate (code 1006). In this case you must add your certificate to root CA (Firefox, Chrome).
// app.js
'use strict'
const https = require('https');
const fs = require('fs');
const ws = require('ws');
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
const index = fs.readFileSync('./index.html');
let server = https.createServer(options, (req, res) => {
res.writeHead(200);
res.end(index);
});
server.addListener('upgrade', (req, res, head) => console.log('UPGRADE:', req.url));
server.on('error', (err) => console.error(err));
server.listen(8000, () => console.log('Https running on port 8000'));
const wss = new ws.Server({server, path: '/echo'});
wss.on('connection', function connection(ws) {
ws.send('Hello');
ws.on('message', (data) => ws.send('Receive: ' + data));
});
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<script>
var socket = new WebSocket('wss://127.0.0.1:8000/echo');
socket.onopen = () => console.log('Connected') || setInterval(() => socket.send(new Date().toLocaleString()), 1000);
socket.onclose = (event) => console.log((event.wasClean) ? 'Disconnected' : 'Connection break: ' + (event.reason || event.code));
socket.onmessage = (event) => console.log('DATA', event.data);
socket.onerror = (err) => console.error(err.message);
</script>
Press F12 to open console...
</body>
</html>

Since you are using Node, I suggest taking a look at Socket.io. It is easier to use than doing all the manual websocket connection logic yourself.

Related

How can I run a websocket server in next js custom server in dev mode

Assuming I want to run a custom next js server, and to accept websocket connections on that same server, how can I avoid clobbering the next js dev server hot reloading which is also using websockets on the same server...
const { createServer } = require('http')
const WebSocket = require("ws")
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = createServer((req, res) => handle(req, res, parse(req.url, true)))
// pass the same server instance that is used by next js to the websocket server
const wss = new WebSocket.Server({ server })
wss.on("connection", async function connection(ws) {
console.log('incoming connection', ws);
ws.onclose = () => {
console.log('connection closed', wss.clients.size);
};
});
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port} and ws://localhost:${port}`)
})
})
I believe this server should work in the production built version, so that the websocket server is created on the same server instance used to handle next js requests, but when I try to do this, the hot module reloading stops working, and errors appear in the chrome dev tools console because websocket connections it expects to be handled by webpack are now being handled by my custom websocket server.
How can I somehow route websocket connections for dev server to next and webpack and others to my own handler?
I know I can run my websocket server on another port, but I want to run it on the same server instance and same port as next js.
So the trick is to create a websocket server with noServer property set to true, and then listen to the server upgrade event, and depending on the pathname, do nothing to allow next js to do it's thing, or pass the request on to the websocket server we created...
const wss = new WebSocket.Server({ noServer: true })
server.on('upgrade', function (req, socket, head) {
const { pathname } = parse(req.url, true);
if (pathname !== '/_next/webpack-hmr') {
wss.handleUpgrade(req, socket, head, function done(ws) {
wss.emit('connection', ws, req);
});
}
});
... all together something like this ...
const { createServer } = require('http')
const WebSocket = require("ws")
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = createServer((req, res) => handle(req, res, parse(req.url, true)))
const wss = new WebSocket.Server({ noServer: true })
wss.on("connection", async function connection(ws) {
console.log('incoming connection', ws);
ws.onclose = () => {
console.log('connection closed', wss.clients.size);
};
});
server.on('upgrade', function (req, socket, head) {
const { pathname } = parse(req.url, true);
if (pathname !== '/_next/webpack-hmr') {
wss.handleUpgrade(req, socket, head, function done(ws) {
wss.emit('connection', ws, req);
});
}
});
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port} and ws://localhost:${port}`)
})
})
Here is my answer to create a webSocket server on Next.js by using an api route instead of creating a custom server.
/pages/api/websocketserverinit.js:
import { WebSocketServer } from 'ws';
const SocketHandler = async (req, res) => {
if (res.socket.server.wss) {
console.log('Socket is already running')
} else {
console.log('Socket is initializing')
const server = res.socket.server
const wss = new WebSocketServer({ noServer: true })
res.socket.server.wss = wss
server.on('upgrade', (req, socket, head) => {
console.log("upgrade", req.url)
if (!req.url.includes('/_next/webpack-hmr')) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req);
});
}
});
wss.on('connection', (ws)=> {
console.log("connection", ws);
ws.on('message', (data) => {
console.log('received: %s', data);
})
ws.send('something');
});
}
res.end()
}
export default SocketHandler
You will have to call the api route to start the websocket server from the client (or server init script):
fetch("http://localhost:3000/api/websocketserverinit")
And then connect to it:
const ws = new WebSocket("ws://localhost:3000")
Not super nice, but may be useful in some case

Https proxy decode encoded data node js

I am trying to setup a proxy for http and https. Here is my code,
const http = require('http');
var https = require('https');
const fs = require('fs');
var url = require('url');
var net = require('net');
const config = require('./config');
let proxify = function (req, res) {
var urlObj = url.parse(req.url);
var target = urlObj.protocol + '//' + urlObj.host;
if (!req.headers['x-target']) req.headers['x-target'] = target;
req.headers['x-proxy-username'] = config.username;
req.headers['x-proxy-password'] = config.password;
console.log(target);
console.log('Proxy HTTP request for:', target);
var proxy = httpProxy.createProxyServer({});
proxy.on('error', function (err, req, res) {
console.log('proxy error', err);
res.end();
});
proxy.web(req, res, { target: config.server, changeOrigin: true });
};
var httpserver = http.createServer(proxify).listen(2890); //this is the port your clients will connect to
const httpsserver = https
.createServer(
{
cert: fs.readFileSync('./ssl_cert/cert.pem'),
key: fs.readFileSync('./ssl_cert/key.pem'),
},
proxify
)
.listen(2891);
var regex_hostport = /^([^:]+)(:([0-9]+))?$/;
var getHostPortFromString = function (hostString, defaultPort) {
var host = hostString;
var port = defaultPort;
var result = regex_hostport.exec(hostString);
if (result != null) {
host = result[1];
if (result[2] != null) {
port = result[3];
}
}
return [host, port];
};
httpserver.addListener('connect', function (req, socket, bodyhead) {
var hostPort = getHostPortFromString(req.url, 443);
var hostDomain = hostPort[0];
var port = parseInt(hostPort[1]);
console.log('Proxying HTTPS request for:', hostDomain, port);
req.headers['x-target'] = 'http://' + hostDomain + ':' + port;
req.headers['x-proxy-username'] = config.username;
req.headers['x-proxy-password'] = config.password;
var proxyHost = new URL(config.server);
var proxySocket = new net.Socket();
proxySocket.connect(
{ port: proxyHost.port, host: proxyHost.hostname },
function () {
console.log('bodyhead', bodyhead.toString()); //debug
proxySocket.write(bodyhead);
socket.write(
'HTTP/' + req.httpVersion + ' 200 Connection established\r\n\r\n'
);
}
);
proxySocket.on('data', function (chunk) {
console.log('proxy data chunk', chunk.toString()); // debug
socket.write(chunk);
});
proxySocket.on('end', function () {
socket.end();
});
proxySocket.on('error', function () {
socket.write('HTTP/' + req.httpVersion + ' 500 Connection error\r\n\r\n');
socket.end();
});
socket.on('data', function (chunk) {
console.log('data chunk', chunk.toString('utf8')); // debug
proxySocket.write(chunk);
});
socket.on('end', function () {
proxySocket.end();
});
socket.on('error', function () {
proxySocket.end();
});
});
Don't judge me too hard, just trying to get it working first.
When proxying http with windows 10 proxy settings, it works fine. But when I am trying to proxy https, it logs encoded data like `↕►♦♦♦☺♣♣♣♠♠☺↕3+)/1.1♣♣☺
☺↔ \s☻�t�DQ��g}T�c\‼sO��♦��U��ޝ∟-☻☺☺+♂
→→♥♦♥♥♥☻♥☺♥☻☻j☺§�` and gives a 400 bad request.I don't know if its the encoding of https response or something else, I have no idea what i am doing at this point and need help.
it is because https uses tls/ssl to encrypt the data.

Nodejs basic auth with https

I have a nodjs app running that uses basic auth with the password details in a file generated by htpasswd. This is working fine.
What I'd like to do is run it over https. I can get it working with https fine, but can't seem to work out how to get the two working together, https + basic auth.
This is for a browser going to the server and sending a variable in the url. I've found a few solutions but many seem to focus on the server doing a outbound call to something else with basic auth.
This is the basic auth one.
// Authentication module.
const auth = require('http-auth');
const basic = auth.basic({
realm: "REALM.",
file: __dirname + "/users.htpasswd"
});
const http = require('http');
const fs = require('fs');
var url = require('url');
const hostname = '0.0.0.0';
const port = 80;
const server = http.createServer(basic, (req, res) => {
var url_parts = url.parse(req.url, true);
var key = url_parts.query["name"];
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
fs.readFile(__dirname + '/basic.json', function (err, data) {
if (err) {
throw err;
}
table = JSON.parse(data.toString());
var value = table[key];
res.end(JSON.stringify({"group" : value}));
});
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
And this is the https bit without the basic auth.
const https = require('https');
const fs = require('fs');
var url = require('url');
const hostname = '0.0.0.0';
const port = 443;
const options = {
key: fs.readFileSync('star.pkey'),
cert: fs.readFileSync('star.pem'),
};
const server = https.createServer(options, (req, res) => {
var url_parts = url.parse(req.url, true);
var key = url_parts.query["name"];
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
fs.readFile(__dirname + '/basic.json', function (err, data) {
if (err) {
throw err;
}
table = JSON.parse(data.toString());
var value = table[key];
res.end(JSON.stringify({"group" : value}));
});
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Any help appreciated.

Nodejs websocket communication with external system

I'm new to nodejs and I'm trying to solve communication issue with external system.
There is a gateway to external system which can handle websocket requests on port 5000. In the example below, when you request homepage, the nodejs opens websocket connection, then on websocket open event it sends request and waits for response which is used for the HTTP response.
Do you know how to open websocket to external system only once and handle requests based on request id?
var ws = require('ws');
var express = require('express');
var async = require('async');
var uuid = require('node-uuid');
app = express();
app.get('/', function (req, res) {
var webSocket = new ws('ws://localhost:5000/');
async.series([
function (callback) {
webSocket.on('open', function () {
webSocket.send(JSON.stringify({query:'data query', requestid: uuid.v4()}));
callback(null, 'data query');
});
},
function (callback) {
webSocket.on('message', function (data, flags) {
callback(null, data);
})
}
], function (err, results) {
res.setHeader('content-type', 'text/javascript');
res.send(results[1]);
webSocket.terminate();
});
});
var server = app.listen(3000, function () {
var port = server.address().port
console.log('Listening at %s', port)
});
Thanks for the hints. I ended with the following solution which does what I expect:
var ws = require('ws');
var express = require('express');
var uuid = require('node-uuid');
var requests = {};
app = express();
var webSocket = new ws('ws://localhost:5000/');
webSocket.on('open', function () {
console.log('Connected!');
});
webSocket.on('message', function (data, flags) {
var json = JSON.parse(data);
console.log(json.requestId);
var res = requests[json.requestId];
res.setHeader('content-type', 'text/javascript');
res.send(json.data);
delete requests[json.requestId];
});
app.get('/', function (req, res) {
var rid = uuid.v4();
requests[rid] = res;
webSocket.send(JSON.stringify({query:'data query', requestId: rid}));
});
var server = app.listen(3000, function () {
var port = server.address().port
console.log('Listening at %s', port)
});

How to create a simple socket in node.js?

I'm trying to create a dummy socket for use in some of my tests
var net = require("net");
var s = new net.Socket();
s.on("data", function(data) {
console.log("data received:", data);
});
s.write("hello!");
Getting this error
Error: This socket is closed.
I've also tried creating the socket with
var s = new net.Socket({allowHalfOpen: true});
What am I doing wrong?
For reference, the complete test looks like this
it("should say hello on connect", function(done) {
var socket = new net.Socket();
var client = Client.createClient({socket: socket});
socket.on("data", function(data){
assert.equal("hello", data);
done();
});
client.connect();
// writes "hello" to the socket
});
I don't think the server is put into listening state. This what I use..
// server
require('net').createServer(function (socket) {
console.log("connected");
socket.on('data', function (data) {
console.log(data.toString());
});
})
.listen(8080);
// client
var s = require('net').Socket();
s.connect(8080);
s.write('Hello');
s.end();
Client only..
var s = require('net').Socket();
s.connect(80, 'google.com');
s.write('GET http://www.google.com/ HTTP/1.1\n\n');
s.on('data', function(d){
console.log(d.toString());
});
s.end();
Try this.
The production code app.js:
var net = require("net");
function createSocket(socket){
var s = socket || new net.Socket();
s.write("hello!");
}
exports.createSocket = createSocket;
The test code: test.js: (Mocha)
var sinon = require('sinon'),
assert = require('assert'),
net = require('net'),
prod_code=require('./app.js')
describe('Example Stubbing net.Socket', function () {
it("should say hello on connect", function (done) {
var socket = new net.Socket();
var stub = sinon.stub(socket, 'write', function (data, encoding, cb) {
console.log(data);
assert.equal("hello!", data);
done();
});
stub.on = socket.on;
prod_code.createSocket(socket);
});
});
We can create socket server using net npm module and listen from anywhere. after creating socket server we can check using telnet(client socket) to interact server.
server.js
'use strict';
const net = require('net');
const MongoClient= require('mongodb').MongoClient;
const PORT = 5000;
const ADDRESS = '127.0.0.1';
const url = 'mongodb://localhost:27017/gprs';
let server = net.createServer(onClientConnected);
server.listen(PORT, ADDRESS);
function onClientConnected(socket) {
console.log(`New client: ${socket.remoteAddress}:${socket.remotePort}`);
socket.destroy();
}
console.log(`Server started at: ${ADDRESS}:${PORT}`);
function onClientConnected(socket) {
let clientName = `${socket.remoteAddress}:${socket.remotePort}`;
console.log(`${clientName} connected.`);
socket.on('data', (data) => {
let m = data.toString().replace(/[\n\r]*$/, '');
var d = {msg:{info:m}};
insertData(d);
console.log(`${clientName} said: ${m}`);
socket.write(`We got your message (${m}). Thanks!\n`);
});
socket.on('end', () => {
console.log(`${clientName} disconnected.`);
});
}
function insertData(data){
console.log(data,'data');
MongoClient.connect(url, function(err, db){
console.log(data);
db.collection('gprs').save(data.msg , (err,result)=>{
if(err){
console.log("not inserted");
}else {
console.log("inserted");
}
});
});
}
using telnet:
$ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hi
We got your message (hi). Thanks!
you need to connect your socket before you can write to it:
var PORT = 41443;
var net = require("net");
var s = new net.Socket();
s.on("data", function(data) {
console.log("data received:", data);
});
s.connect(PORT, function(){
s.write("hello!");
});
It will useful code for websocket
'use strict';
const express = require('express');
const { Server } = require('ws');
const bodyParser = require('body-parser');
const cors = require('cors');
const PORT = process.env.PORT || 5555;
const INDEX = '/public/index.html';
const router = express.Router();
var urlencodedParser = bodyParser.urlencoded({ extended: false });
router.get('/', function(req, res) {
res.sendFile(INDEX, { root: __dirname });
});
const server = express()
.use(router)
.use(bodyParser.json())
.use(cors)
.listen(PORT, () => {
console.log(`Listening on ${PORT}`)
});
const wss = new Server({ server });
wss.on('connection', (ws) => {
ws.on('message', message => {
var current = new Date();
console.log('Received '+ current.toLocaleString()+': '+ message);
wss.clients.forEach(function(client) {
client.send(message);
var getData = JSON.parse(message);
var newclip = getData.clipboard;
var newuser = getData.user;
console.log("User ID : "+ newuser);
console.log("\nUser clip : "+ newclip);
});
});
});

Resources