WebSocket Proxy failing with Node.js and Angular. Unexpected response code: 404 - node.js

I am trying to get up to speed on proxying websockets. I have some angular code that I modified from a tutorial which tests websocket connections against echo.websocket.org. The code works fine by itself.
<!DOCTYPE html>
<html>
<meta charset = "utf-8" />
<title>WebSocket Test</title>
<script language = "javascript" type = "text/javascript">
var wsUri = "ws://echo.websocket.org/";
//var wsUri = "ws://localhost:8015"
var output;
var success = 0;
var failure = 0;
function init() {
output = document.getElementById("output");
output.innerHTML = "";
testWebSocket();
writeScore();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) {
onOpen(evt)
};
websocket.onclose = function(evt) {
onClose(evt)
};
websocket.onmessage = function(evt) {
onMessage(evt)
};
websocket.onerror = function(evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
success = success +1;
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style = "color: blue;">RESPONSE: ' +
evt.data+'</span>'); websocket.close();
}
function onError(evt) {
writeToScreen('<span style = "color: red;">ERROR:</span> '
+ evt.data);
failure = failure +1;
}
function doSend(message) {
writeToScreen("SENT: " + message); websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
function writeScore(){
var pScore = document.createElement("p");
pScore.style.wordWrap = "break-word";
pScore.innerHTML = "Success "+ success+ " Failures "+ failure;
output.appendChild(pScore);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id = "output"></div>
</html>
following the instructions from https://github.com/nodejitsu/node-http-proxy#proxying-websockets
I have tried to set up a simple proxy 2 different ways that will forward the angular page's websocket requests to the same echo.websocket.org connection by altering the wsUri variable in the angular code to point at one of the servers created in this code.
///
// Setup our server to proxy standard HTTP requests
//
var httpProxy = require('http-proxy');
var http = require('http');
var proxy = new httpProxy.createProxyServer({
target: {
host: 'echo.websocket.org',
port: 80
}
});
var proxyServer = http.createServer(function (req, res) {
proxy.web(req, res);
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
proxyServer.on('upgrade', function (req, socket, head) {
console.log(socket);
proxy.ws(req, socket, head);
});
proxyServer.listen(8015);
var test = httpProxy.createServer({
target: 'ws://echo.websocket.org',
ws: true
});
test.listen(8014);
Unfortunately the proxied request (for both the sever listening on port 8014 and 8015) triggers the angular websocket.onerror event handler providing me a less than helpful message of 'undefined' for evt.data.
Update:
Chrome displays the following error message in developer tools when I try to open the webpage using the localhost proxies.
index.htm:29 WebSocket connection to 'ws://localhost:8015/' failed:
Error during WebSocket handshake: Unexpected response code: 404
Firefox displays the following in it's "Web Console"
Firefox can’t establish a connection to the server at ws://localhost:8015/.
However, the upgrade event is firing as I am seeing the Node's console display the socket information from the console.log() line of the Node JS code. That leads me to believe that the localhost server is being found, begging the question for the origin of the 404 error. I'm not sure if it is related to the finding the proxy (which is having its http upgrade event triggered), or the proxy's target.
How do I get Node JS to proxy the angular websocket connection that works fine when no proxy is between the browser and echo.websocket.org?

TLDR;
it is related to setting up the Host: foo.com header before forwarding the proxied request.
Basically, Error during WebSocket handshake: Unexpected response code: 404
means that it actually could not establish any connection with given that wsUri.
As seen from this issue, changeOrigin proposed to be true as default.
However, it is false in our case (by default). So, forwarded request is invalid/malformed without properly setting up the HTTP Host header. You may try to debug this further to see the real HTTP attributes, headers, etc.
Also,
autoRewrite attribute rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port.
Try to add properties below as createServer() arguments:
autoRewrite: true,
changeOrigin: true,
So, it becomes:
const test = httpProxy.createServer({
target: 'ws://echo.websocket.org',
ws: true,
autoRewrite: true,
changeOrigin: true,
});
test.listen(8014);
Hope it helps!

Related

What are the minimum requirements to get a secure WebSocket working with Node.js?

When it comes to WebSockets, it seems the internet has plenty of ws:// (insecure) tutorials, but hardly any on wss:// (secure). I've got an insecure websocket connection working fine with Node.js thanks to Supun Kavinda's tutorial, but can't get it to work as a secure connection.
Here's the summarized working code for the insecure connection (fake ip address):
File: js-index.js
window.WebSocket = window.WebSocket || window.MozWebSocket;
var connection = new WebSocket('ws://3.00.00.00:8080');
File: server.js
const https = require('https');
const fs = require('fs');
var WebsocketServer = require('websocket').server;
var server = https.createServer(function(request,response) {
function getPostParams(request, callback) {
var qs = require('querystring');
if (request.method == 'POST') {
var body = '';
request.on('data', function (data) {
body += data;
// Too much POST data, kill the connection!
if (body.length > 1e6)
request.connection.destroy();
});
request.on('end', function () {
var POST = qs.parse(body);
callback(POST);
});
}
}
if (request.method === "POST") {
getPostParams(request, function(POST) {
messageClients(POST.data);
response.writeHead(200);
response.end();
});
return;
}
});
server.listen(8080);
/*Handling websocket requests*/
var websocketServer = new WebsocketServer({
httpServer: server
});
websocketServer.on("request", websocketRequest);
global.clients = {}; // connected clients
var connectionId = 0;
function websocketRequest(request) {
var connection = request.accept(null, request.origin);
connectionId++;
clients[connectionId] = connection;
}
function messageClients(message) {
for (var i in clients) {
clients[i].sendUTF(message);
}
}
For a secure connection, I have tried many things, mainly centered on changing ws:// to wss://, and including a .pem certificate/key in the createServer() call, like so:
const ssl_creds = {
cert: fs.readFileSync('/opt/bitnami/apache2/htdocs/fullchain.pem'),
key: fs.readFileSync('/opt/bitnami/apache2/htdocs/privkey.pem')
};
var server = https.createServer(ssl_creds, function(request,response) {....
However, I continue to get errors (only testing in Chrome thus far), such as:
Error in connection establishment: net::ERR_CERT_COMMON_NAME_INVALID
Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
^#1 being the problem I encounter most
I'm using the same ssl credentials granted by LetsEncrypt for my website itself - I assume that is okay?
In summary, most of the tutorials I've found either do something exactly like this, or talk about how something like NGINX is required to setup proxies/tunnels - something I am completely clueless on.
Can someone tell me if proxy configurations are typically required for this, or if I'm just missing something small/dumb? Any insight at all would be appreciated. In case it's relevant, I'm just cutting my teeth on AWS with a Lightsail PHP image from Bitnami.

http-proxy-rules and Websockets

I haven't been able to find any documentation/answer to my needs.
I'm in something of a pickle. I'm developing a websocket application which will allow module expansion by creating new services (websocket servers). Of course this means more ports to connect to. The problem is, our corporate policy only has a very few ports open so I need to proxy my requests.
I've read many answers saying to use NGINX, but I simply can't. First off I'm running windows, second our company is very strict on what can and can't be used. I am able to install any node module, though. I've attempted to use the http-proxy module along with http-proxy-rules.
The problem is, I'm getting 404's on every websocket request. I'll note that the default proxy (for normal webservice, not sockets) is working 100% fine.
Here's my current code:
var http = require('http'),
httpProxy = require('http-proxy'),
HttpProxyRules = require('http-proxy-rules');
// Set up proxy rules instance
var proxyRules = new HttpProxyRules({
rules: {
'.*/ws/admin': 'http://localhost:26266', // Rule for websocket service (admin module)
'.*/ws/quickquery': 'http://localhost:26265' // Rule for websocket service (quickquery module)
},
default: 'http://Surface.levisinger.com:8080' // default target
});
// Create reverse proxy instance
var proxy = httpProxy.createProxy();
// Create http server that leverages reverse proxy instance
// and proxy rules to proxy requests to different targets
http.createServer(function(req, res) {
// a match method is exposed on the proxy rules instance
// to test a request to see if it matches against one of the specified rules
var target = proxyRules.match(req);
if (target) {
//console.log(req);
console.log("Returning " + target + " for " + req.headers.host);
return proxy.web(req, res, {
target: target,
ws: true
});
}
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('The request url and path did not match any of the listed rules!');
}).listen(5050);
My client code connecting to the websocket looks like this:
var servPath = (cliSettings["AppPaths"]["Admin"] == null) ? 'http://' + window.location.hostname + ':5050' : cliSettings["AppPaths"]["Admin"],
AdminIO = new io(servPath, {
extraHeaders: {
Service: "Admin"
},
path: '/ws/admin'})
...
And the websocket server is called like this:
io = require('socket.io').listen(26266,{ path: '/ws/admin'}) // can use up to 26484
I really hope someone here will have an idea. Thanks!
Figured out how to do this. There are a few things to this...
1. You have to use custom websocket paths if you want to proxy them.
2. You must give the entire path to the websocket when proxying them.
3. You'll need to specify an event to handle websocket (ws) traffic.
Here's an example for you all.
+++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++
Proxy Server:
var ini = require('node-ini'),
conf = ini.parseSync('../settings/config.ini'),
http = require('http'),
httpProxy = require('http-proxy'),
HttpProxyRules = require('http-proxy-rules');
// Set up proxy rules instance
var proxyRules = new HttpProxyRules({
rules: {
'.*/ws/remote': 'http://' + conf["Server"]["binding"] + ':26267/ws/remote',
'.*/ws/admin': 'http://' + conf["Server"]["binding"] + ':26266/ws/admin', // Rule for websocket service (admin module)
'.*/ws/quickquery': 'http://' + conf["Server"]["binding"] + ':26265/ws/quickquery' // Rule for websocket service (quickquery module)
},
default: 'http://' + conf["Server"]["binding"] + ':8080' // default target
});
// Create reverse proxy instance
var proxy = httpProxy.createProxy();
// Create http server that leverages reverse proxy instance
// and proxy rules to proxy requests to different targets
var proxyServer = http.createServer(function(req, res) {
// a match method is exposed on the proxy rules instance
// to test a request to see if it matches against one of the specified rules
var target = proxyRules.match(req);
if (target) {
//console.log(req.url);
//console.log("Returning " + target + " for " + req.url);
return proxy.web(req, res, {
target: target,
ws: true
});
}
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('The request url and path did not match any of the listed rules!');
}).listen(conf["Server"]["port"]);
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
proxyServer.on('upgrade', function (req, socket, head) {
var target = proxyRules.match(req);
if (target) {
return proxy.ws(req, socket, head, { target: target });
}
});
process.on('SIGINT', function() {
db.stop(function(err) {
process.exit(err ? 1 : 0);
});
});
Websocket Server socket listener:
io = require('socket.io').listen(26266,{ path: '/ws/admin'});
Connect to websocket from client page:
AdminIO = new io({path: '/ws/admin'});
+++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++
The example above will proxy my "admin" connection, which is running on port 26266, through port 80. (I'd of course recommend using 443/SSL in every situation, but that's a bit more complex).
Hope this helps someone!

Failed to load resource: the server responded with a status of 426 (Upgrade Required)

I tried to go with the tutorial of this link http://web-engineering.info/node/57
But when I execute node server.js and open the browser http://localhost:3434 it says upgrade required. The server.js file is:
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({port: 3434});
wss.broadcast = function (data) {
var i = 0, n = this.clients ? this.clients.length : 0, client = null;
for (; i < n; i++) {
client = this.clients[i];
if (client.readyState === client.OPEN) {
client.send(data);
}
else console.error('Error: the client state is ' + client.readyState);
}
};
wss.on('connection', function (ws) {
ws.on('message', function (message) {
wss.broadcast(message);
});
});
you have to open your index.html in browser not http://127.0.0.1:3434
its a websocket server. You are trying to make a http connection to a websocket server.
Most probably your server socket at localhost:3434 don't have support for websocket, so the connection is terminated by the client browser.
This error indicates that on localhost:3434 you are running a HTTP server which is incapable to "upgrade" to websocket.
(Since both simple http and websocket begins with a simple http request. In that http request the client ask the server to switch to websocket protocol.)
Should you add this ?
var ws = require('websocket.io')
, server = new ws.Server()
// … somewhere in your http server code
server.on('upgrade', function (req, socket, head) {
server.handleUpgrade(req, socket, head);
});
ref https://www.npmjs.com/package/websocket.io#passing-in-requests
Check this SO too What is an http upgrade?
I tried intercepting the http request
var ws = require('websocket.io')
, http = require('http').createServer().listen(3000)
, server = ws.attach(http)
server.on('connection', function (socket) {
socket.on('message', function () { });
socket.on('close', function () { });
});
https://www.npmjs.com/package/websocket.io#passing-in-requests
For me using the npmjs documentation, I went copy-paste rogue.
Then, debugging my client-side request I noted that the URL parameter I was using was not a string as expected.
But it was shark_s's answer that helped remind me to go look at the console and interpret the error again- so thanks.

node-http-proxy load balance websocket error

I have just started evaluating the node-http-proxy because I need to have a scalable web socket server.
I have tested the ’simple-balancer-with-websockets’ example provided in the repository but it does not work when acting as a proxy to multiple addresses. It only works as a proxy for one address!
When proxying to multiple addresses a WebSocket hangup error as follows:
Error: socket hang up
at createHangUpError (http.js:1472:15)
at Socket.socketOnEnd [as onend] (http.js:1568:23)
at Socket.g (events.js:180:16)
at Socket.EventEmitter.emit (events.js:117:20)
at _stream_readable.js:920:16
at process._tickCallback (node.js:415:13)
I am using:
node 0.10.26
socket io 1.0.6
node-http-proxy 1.1.5
platform OSX
The following is the load-balancer. Its only difference to the provided sample is the addresses used and the listen port.
var http = require('http'),
httpProxy = require('http-proxy');
//
// A simple round-robin load balancing strategy.
//
// First, list the servers you want to use in your rotation.
//
var addresses = [
{
host: 'localhost',
port: 8000
},
{
host: 'localhost',
port: 8001
},
{
host: 'localhost',
port: 8002
}
];
//
// Create a HttpProxy object for each target
//
var proxies = addresses.map(function (target) {
return new httpProxy.createProxyServer({
target: target
});
});
//
// Get the proxy at the front of the array, put it at the end and return it
// If you want a fancier balancer, put your code here
//
function nextProxy() {
var proxy = proxies.shift();
proxies.push(proxy);
return proxy;
}
//
// Get the 'next' proxy and send the http request
//
var server = http.createServer(function (req, res) {
nextProxy().web(req, res);
});
//
// Get the 'next' proxy and send the upgrade request
//
server.on('upgrade', function (req, socket, head) {
nextProxy().ws(req, socket, head);
});
server.listen(9000);
The basic http server acting as a target for the above load-balancer is:
var http = require('http'),
fs = require('fs'),
io = require('socket.io');
var args = process.argv.splice(2);
var port = args[0] || 8000;
server = http.createServer(function(req, res) {
var filePath = (__dirname + '/public/connect.html');
fs.readFile(filePath,function (err, data){
res.writeHead(200, {'Content-Type': 'text/html','Content-Length':data.length});
res.write(data);
res.end();
});
});
server.listen(port, function() {
console.log('ws listening on: ' + port);
});
io = io(server);
io.on('connect', function(socket){
console.log('socket connected');
socket.emit('message', 'ws message from ' + port);
});
The client html is:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('message', function (data) {
console.log(data);
});
</script>
</head>
<body>
node-http-proxy basic load balance test with websockets
</body>
</html>
I regard this as a basic test and yet it does not work! Can anyone explain what I am doing wrong and suggest a solution please?
Many thanks for any thoughts.
Socket.io 1.0 needs sticky sessions. See socket.io/docs/using-multiple-nodes
First engine.io makes an xhr request then it makes an websocket request. Both requests need to reach the same socket.io server. Even more so if engine.io needs to fallback to long polling etc . . .
To fix it, you just need to make your proxy server session aware. It can still round robin fresh connections, but as soon as it serves a socket.io request it needs to route subsequent requests from that session to the same backend.

How can I create a Twitter stream using Node.js and Websockets?

A few months ago (August 2011) I successfully created a node.js websockets server which connected to Twitter's Streaming API using basic HTTP user/password authentication. To do this, I employed Andre Goncalves' twitter-nodejs-websocket library.
Since creating this working implementation, Twitter has eliminated access to the streaming API via basic HTTP auth, in favor of OAuth. After this shift, I utilized Ciaran Jessup's node-oauth library, which has successfully given me access to the Streaming API again (when I run the server I am successfully outputting the tweets via console.log(tweet) -- see below ).
The problem now is that my websockets server is no longer working. When I run my server from the command line and hit the client web page from the browser, the websocket "onclose" event is immediately fired.
I've tried everything I can think of to get this working. Any help would be very greatly appreciated!
server.js
var sys = require('sys'),
http = require('http'),
ws = require("./vendor/ws"),
base64 = require('./vendor/base64'),
arrays = require('./vendor/arrays')
var OAuth = require('./oauth/oauth').OAuth;
var consumer_key = '[...]'; //removed for obvious security reasons...
var consumer_secret = '[...]';
var access_token = '[...]';
var access_token_secret = '[...]';
oa = new OAuth("https://twitter.com/oauth/request_token",
"https://twitter.com/oauth/access_token",
consumer_key,
consumer_secret,
"1.0A",
null,
"HMAC-SHA1");
var request = oa.get("https://stream.twitter.com/1/statuses/filter.json?track=google", access_token, access_token_secret );
// Response Parsing -------------------------------------------- //
var clients = [];
var message = "";
request.addListener('response', function (response) {
response.setEncoding('utf8');
response.addListener("data", function (chunk) {
message += chunk;
var newlineIndex = message.indexOf('\r');
// response should not be sent until message includes '\r'.
// Look at the section titled "Parsing Responses" in Twitter's documentation.
if (newlineIndex !== -1) {
var tweet = message.slice(0, newlineIndex);
clients.forEach(function(client){
// Send response to all connected clients
client.write(tweet);
});
// this just tests if we are receiving tweets -- we are: terminal successfully outputs stream //
var pt = JSON.parse(tweet);
console.log('tweet: ' + pt.text);
}
message = message.slice(newlineIndex + 1);
});
});
request.end();
// Websocket TCP server
ws.createServer(function(websocket){
clients.push(websocket);
websocket.addListener("connect", function(resource){
// emitted after handshake
sys.debug("connect: " + resource);
}).addListener("close", function(){
// emitted when server or client closes connection
clients.remove(websocket);
sys.debug("close");
});
}).listen(8081);
// This basic http server works, so we know this port is open.
//
// var http = require('http');
// http.createServer(function (req, res) {
// res.writeHead(200, {'Content-Type': 'text/plain'});
// res.end('Hello World\n');
// }).listen(8081);
client code
<script type="text/javascript" charset="utf-8">
ws = new WebSocket("ws://ec2-67-202-6-10.compute-1.amazonaws.com:8081");
ws.onmessage = function(evt) {
console.log('tweet')
};
ws.onclose = function() {
console.log("socket closed");
};
ws.onopen = function() {
console.log("connected...");
};
</script>
Maybe you updated the browser? The websocket spec is chaning rapidly. Anyway, I'd propose using socket.io because it will even still work with fallbacks if the browser is outdated or websockets got incompatible again or a crappy proxy is preventing websockets from working.
Have a look at this sample event stream (it uses server sent events) from a twitter stream:
https://github.com/chovy/nodejs-stream

Resources