Node.js proxy server with https support - windows

I'm trying to write proxy server that will proxy (almost) all http/s requests. Almost all because I need catch requests for some specific https url's and as response send the file from hdd instead of real response from the web.
Whole solution should works as proxy in the browser and have to work on windows 7. I started with my own proxy based on express.js. It works great ... but unfortunately not via https. Then I was trying to use several existing node.js proxy servers from github (https://github.com/horaci/node-mitm-proxy, https://github.com/Hypermediaisobar/hyperProxy and few other) but any of them worked in windows environment on https (or I don't know how to congiure them).
Finally I found somewhere in internet code (don't have the link to source) which works via https (see code below). The problems with this code is, that I can't find right way to check the incoming request url and depending on the request url handle them in different ways.
I will be grateful if someone could help me with that.
var http = require('http');
var net = require('net');
var debugging = 0;
var regex_hostport = /^([^:]+)(:([0-9]+))?$/;
function getHostPortFromString(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 ] );
}
// handle a HTTP proxy request
function httpUserRequest(userRequest, userResponse) {
var httpVersion = userRequest['httpVersion'];
var hostport = getHostPortFromString(userRequest.headers['host'], 80);
// have to extract the path from the requested URL
var path = userRequest.url;
result = /^[a-zA-Z]+:\/\/[^\/]+(\/.*)?$/.exec(userRequest.url);
if (result) {
if (result[1].length > 0) {
path = result[1];
} else {
path = "/";
}
}
var options = {
'host': hostport[0],
'port': hostport[1],
'method': userRequest.method,
'path': path,
'agent': userRequest.agent,
'auth': userRequest.auth,
'headers': userRequest.headers
};
var proxyRequest = http.request(
options,
function (proxyResponse) {
userResponse.writeHead(proxyResponse.statusCode, proxyResponse.headers);
proxyResponse.on('data', function (chunk) {
userResponse.write(chunk);
}
);
proxyResponse.on('end',
function () {
userResponse.end();
}
);
}
);
proxyRequest.on('error', function (error) {
userResponse.writeHead(500);
userResponse.write(
"<h1>500 Error</h1>\r\n<p>Error was <pre>" + error + "</pre></p>\r\n</body></html>\r\n";
);
userResponse.end();
}
);
userRequest.addListener('data', function (chunk) {
proxyRequest.write(chunk);
}
);
userRequest.addListener('end', function () {
proxyRequest.end();
}
);
}
function main() {
var port = 5555; // default port if none on command line
// check for any command line arguments
for (var argn = 2; argn < process.argv.length; argn++) {
if (process.argv[argn] === '-p') {
port = parseInt(process.argv[argn + 1]);
argn++;
continue;
}
if (process.argv[argn] === '-d') {
debugging = 1;
continue;
}
}
if (debugging) {
console.log('server listening on port ' + port);
}
// start HTTP server with custom request handler callback function
var server = http.createServer(httpUserRequest).listen(port);
server.addListener('checkContinue', function (request, response){
console.log(request);
response.writeContinue();
});
// add handler for HTTPS (which issues a CONNECT to the proxy)
server.addListener(
'connect',
function (request, socketRequest, bodyhead) {
var url = request['url'];
var httpVersion = request['httpVersion'];
var hostport = getHostPortFromString(url, 443);
// set up TCP connection
var proxySocket = new net.Socket();
proxySocket.connect(
parseInt(hostport[1]), hostport[0],
function () {
console.log("ProxySocket: " + hostport[1] + " | " + hostport[0]);
proxySocket.write(bodyhead);
// tell the caller the connection was successfully established
socketRequest.write("HTTP/" + httpVersion + " 200 Connection established\r\n\r\n");
}
);
proxySocket.on('data', function (chunk) {
socketRequest.write(chunk);
}
);
proxySocket.on('end', function () {
socketRequest.end();
}
);
socketRequest.on('data', function (chunk) {
proxySocket.write(chunk);
}
);
socketRequest.on('end', function () {
proxySocket.end();
}
);
proxySocket.on('error', function (err) {
socketRequest.write("HTTP/" + httpVersion + " 500 Connection error\r\n\r\n");
socketRequest.end();
}
);
socketRequest.on('error', function (err) {
proxySocket.end();
}
);
}
); // HTTPS connect listener
}
main();

are you asking for
http://expressjs.com/4x/api.html#req.secure
req.secure -> https
http://expressjs.com/4x/api.html#req.protocol
req.protocol -> http
http://expressjs.com/4x/api.html#req.host
req.host
req.url
this should all be on your userRequest
I probably did not understand your question correctly.

Just add this line:
var https = require('https');
And when you are making regular http requests, use http.request, and for the ssl requests, https.request.

Related

using https.get instead of got causes a 308

this is a difficult question to ask because I am mystified, but let's see…
I am comparing Got with https.get, and have the following, bare simple code that works. Both Got and https.get return exactly the same result.
But when I use exactly the same code in my Fastify application, Got works as expected but https.get results in a 308.
Is there some way I can debug this code to see what is being sent out by https.get that is causing the remote server to respond with a 308 instead of 200?
import got from 'got';
import https from 'https';
const withGot = async (uri) => {
try {
const json = JSON.parse((await got(uri)).body);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const withHttps = async (uri) => {
try {
const json = await getRequest(uri);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const getRequest = async (uri) => {
return new Promise((resolve) => {
https.get(uri, (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
/**
* Any 2xx status code signals a successful response but
* here we're only checking for 200.
**/
if (statusCode !== 200) {
error = new Error(`ERROR\n${'-'.repeat(50)}\nRequest Failed.\nURI: ${uri}\nStatus Code: ${statusCode}`);
}
else if (!/^application\/json/.test(contentType)) {
error = new Error(`Invalid content-type.\nExpected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
/**
* Consume response data to free up memory
**/
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
}
catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
});
}
const uri = 'https://zenodo.org/api/records/?q=phylogeny';
withGot(uri);
withHttps(uri);
I figured out the reason for the problem (and the solution)… seems like when I use https.get, I still have to pass the options with a port 443 (the default port for https), otherwise, https seems to knock on port 80 and then gets redirected to port 443 which results in the server sending back html which causes the JSON parser to croak. If I pass an options object like below, then it works. But, it is still weird that the standalone script works fine without the options, so I continue to be mystified even though I have found a solution.
const options = {
hostname: 'zenodo.org',
port: 443,
path: `/api/records/?${qs}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};

socket.io switch between localhost & server

I am running socket.io on node.js on a local Mac machine.
I have setup port forwarding
I am using the following code:
socket = io.connect('XXX.XXX.137.143:1337');
socket.on('connect_failed', function(){
$.jGrowl('Connection Failed');
});
socket.on('disconnect', function(){
$.jGrowl('disconnected');
});
socket.on('connect', function () {
$.jGrowl('We are connected ' );
});
The IP address is my router IP address. This all works as expected.
However if I run the app on the same network as the router it fails to connect. It needs :
socket = io.connect('localhost'); // or the ip of the machine.
How do I determine I am on the same network as the machine and switch the IP setting ?
I tried:
if (location.hostname === "localhost" || location.hostname === "192.168.1.1")
{$.jGrowl("It's a local client!");
var IPaddress = 'http://192.168.1.6:1337';}
else {var IPaddress = '92.12.137.143:1337';
$.jGrowl("It's a remote client!")
}
This works locally but fails on another network that has IP = 192.168.1.1.
Cheers
Steve Warby
You could first attempt a connection to the public IP address and, if that fails, then attempt to connect locally. Here's one way to do that:
let socket = io.connect('http://92.12.137.143:1337');
socket.on('connect_error', handleNoConnect);
socket.on('connect_timeout', handleNoConnect);
socket.on('connect', onConnect);
function handleNoConnect() {
console.log("No connection to http://92.12.137.143:1337");
socket = io.connect('http://192.168.1.6:1337');
socket.on('connect_error', handleNoConnect2);
socket.on('connect_timeout', handleNoConnect2);
socket.on('connect', onConnect);
}
function handleNoConnect2() {
console.log("No connection to http://192.168.1.6:1337");
// decide what to do when you can't connect to either
}
function onConnect() {
console.log("connected");
// set other event handlers on a connected socket
socket.on('disconnect', function() {
console.log("disconnected");
});
}
Or, here's a more general purpose implementation that lets you pass in an array of URLs to try connecting to and it returns a promise that will resolve with the socket that made the connection or reject with an appropriate error.
function connect(list) {
let index = 0;
return new Promise((resolve, reject) => {
function next() {
if (index < list.length) {
let url = list[index++];
let doneThis = false;
let socket = io.connect(url);
socket.on('connect_error', handleNoConnect);
socket.on('connect_timeout', handleNoConnect);
socket.on('connect', () => {
// set property on socket object that tells you
// which URL it connected to
socket._url = url;
resolve(socket);
});
function handleNoConnect() {
if (!doneThis) {
doneThis = true;
console.log("No connection to " + url);
// try next item in the list
next();
}
}
} else {
reject(new Error("No connections succeeded"));
}
}
if (!list || !list.length) {
reject(new Error("Must pass array of URLs to connect()"));
return;
}
next();
});
}
connect(['http://92.12.137.143:1337', 'http://192.168.1.6:1337']).then(socket => {
// connected here
console.log("connected to " + socket._url);
// configure event handlers on the connected socket object here
socket.on('xxx', ...);
}).catch(err => {
// all connections failed here
console.log(err);
})

Laravel IO Socket and redis over SSL https connection

Currently have IO sockets with laravel broadcasting with redis working perfectly. Until I then set up an SSL cert on the domain.
I have redis-server running on port 3001.
Then there is a socket.js set to listen to 3000.
My JS on the page I listen via io('//{{ $listen }}:3000').
Any guidance would be great on how to get this working over https. Would I just use 443 as the port?
Thanks.
My socket.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.subscribe('notifications', function(err, count) {
});
redis.on('message', function(channel, message) {
console.log('Message Recieved: ' + message);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function(){
console.log('Listening on Port 3000');
});
First, setup your serverOptions object:
var serverOptions = {
port: 3000,
host: 127.0.0.1, //address to your site
key: '/etc/nginx/ssl/your_site/server.key', //Or whatever the path to your SSL is
cert: '/etc/nginx/ssl/your_site/server.crt',
NPNProtocols: ['http/2.0', 'spdy', 'http/1.1', 'http/1.0']
}
For the NPNProtocols, you may not care for all of them, but they're provided for reference.
Now just create the server:
var app = require('https').createServer(serverOptions),
io = require('socket.io')(app);
This should be pretty plug and play into your source at his point.\
As a side note your stuff is completely wide open and anyone can listen on your web socket, so nothing should be sent that is private through here. If you need to make your data private, then you're going to need 1 of two things;
Something like JWT-Auth Token
Something custom that interface with the Redis queue:
Here's an example of the latter:
var SECRET_KEY = '<YOUR_LARAVEL_SECRET_KEY>';
var laravel_session_parser = {
ord: function (string) {
return string.charCodeAt(0);
},
decryptSession: function (cookie, secret) {
if (cookie) {
var session_cookie = JSON.parse(new Buffer(cookie, 'base64'));
var iv = new Buffer(session_cookie.iv, 'base64');
var value = new Buffer(session_cookie.value, 'base64');
var rijCbc = new mcrypt.MCrypt('rijndael-128', 'cbc');
rijCbc.open(secret, iv);
var decrypted = rijCbc.decrypt(value).toString();
var len = decrypted.length - 1;
var pad = laravel_session_parser.ord(decrypted.charAt(len));
return phpunserialize.unserialize(decrypted.substr(0, decrypted.length - pad));
}
return null;
},
getUidFromObj: function (obj, pattern) {
var regexp = /login_web_([a-zA-Z0-9]+)/gi;
if (pattern) {
regexp = pattern;
}
var u_id = null;
for (var key in obj) {
var matches_array = key.match(regexp);
if (matches_array && matches_array.length > 0) {
u_id = obj[matches_array[0]];
return u_id;
}
}
return u_id;
},
getRedisSession: function (s_id, cb) {
var _sessionId = 'laravel:' + s_id;
client.get(_sessionId, function (err, session) {
if (err) {
cb && cb(err);
return;
}
cb && cb(null, session);
});
},
getSessionId: function (session, _callback) {
var u_id = null,
err = null;
try {
var laravelSession = phpunserialize.unserialize(phpunserialize.unserialize(session));
u_id = laravel_session_parser.getUidFromObj(laravelSession);
} catch (err) {
_callback(err, null);
}
_callback(err, u_id);
},
ready: function (socket, _callback) {
if (typeof socket.handshake.headers.cookie === 'string') {
var cookies = cookie.parse(socket.handshake.headers.cookie);
var laravel_session = cookies.laravel_session;
var session_id = laravel_session_parser.decryptSession(laravel_session, SECRET_KEY);
laravel_session_parser.getRedisSession(session_id, function (err, session) {
if (!err && session) {
laravel_session_parser.getSessionId(session, function (err, user_id) {
if (user_id) {
_callback(null, session_id, user_id, laravel_session)
} else {
_callback(new Error('Authentication error'), null);
}
});
} else {
_callback(new Error('Authentication error'), null);
}
});
}
}
};
Now you can just have IO get an instance of the individuals session when they establish a connection to socket.io
io.on('connection', function (socket) {
laravel_session_parser.ready(socket, function(err, session_id, user_id, laravel_session) {
//log out the variables above to see what they provide
});
});
Note, I prefer to use dotenv in NodeJS to share environment variables between Laravel and Node.
Then you can do process.env.APP_KEY and you don't need to worry about sharing variables.
Also of note, that script above is not complete and is not production ready, it's just meant to be used as an example.

What is the Alternative to "Remote Address" header in FireFox?

In Google chrome there is a header called, Remote Address.
I'm writing an add-on for Firefox and I need to decide something based on the remote host but it looks like there is no such header in Firefox.
If you know how to access remote host from the observer object please tell me.
observe : function(aSubject, aTopic, aData) {
//I need remote host here
}
her is the screen shot to the header in Google chrome
If the header is not there it throws excetption NS_ERROR_NOT_AVAILABLE
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import('resource://gre/modules/Services.jsm');
var httpRequestObserver =
{
observe: function(subject, topic, data)
{
var httpChannel, requestURL;
httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
requestURL = httpChannel.URI.spec;
if (topic == "http-on-modify-request") {
//if (requestURL.indexOf('google.com') > -1) {
//httpChannel.setRequestHeader('MyCustomRequestHeader', 'hiiii', false);
try {
var Host = httpChannel.getRequestHeader('Host');
} catch (ex) {
var Host = 'NULL';
}
console.log('REQUEST Header "Host" = ' + Host);
//}
} else if (topic == "http-on-examine-response") {
try {
var Host = httpChannel.getResponseHeader('Host');
} catch (ex) {
var Host = 'NULL';
}
console.log('RESPONSE Header "Host" = ' + Host);
}
}
};
Services.obs.addObserver(httpRequestObserver, "http-on-modify-request", false);
Services.obs.addObserver(httpRequestObserver, "http-on-examine-response", false);
//Services.obs.removeObserver(httpRequestObserver, "http-on-modify-request", false); //run this on shudown of your addon otherwise the observer stags registerd
//Services.obs.removeObserver(httpRequestObserver, "http-on-examine-response", false); //run this on shudown of your addon otherwise the observer stags registerd
Useful articles used to make this snippet:
https://developer.mozilla.org/en-US/docs/Observer_Notifications#HTTP_requests
https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIHttpChannel#getRequestHeader%28%29

Control flow with XmlHttpRequest?

XmlHttpRequest works through callbacks. So how can I return a value? I tried to set a global variable, but that doesn't seem to be working.
var response = null; // contains the most recent XmlHttpRequest response
// loads the info for this username on the page
function loadUsernameInfo(username) {
getUserInfo(username);
var profile = response;
if (profile) {
// do stuff
}
else {
indicateInvalidUsername(username);
}
}
getUserInfo() can't return a result, because of the callback:
function getUserInfo(username) {
var request = createRequest();
request.onreadystatechange = userObjFromJSON;
var twitterURL = "http://twitter.com/users/show/" + escape(username) + ".json";
var url = "url.php?url=" + twitterURL;
request.open("GET", url, true);
request.send(null);
}
The callback:
function userObjFromJSON() {
if (this.readyState == 4) {
alert(this.responseText);
response = this.responseText;
}
}
How can I get the response back to loadUsernameInfo()?
You can do synchronous requests, though it is not recommended - the A is for Asynchronous... But the general idea to implement this correctly would be:
var response = null; // contains the most recent XmlHttpRequest response
// loads the info for this username on the page
function loadUsernameInfo(username) {
getUserInfo(username, onLoadUsernameComplete);
}
function getUserInfo(username, oncomplete) {
var request = createRequest();
request.__username = username;
request.onreadystatechange = oncomplete;
var twitterURL = "http://twitter.com/users/show/" + escape(username) + ".json";
var url = "url.php?url=" + twitterURL;
request.open("GET", url, true);
request.send(null);
}
function onLoadUsernameComplete(req) {
if (req.readyState == 4) {
// only if "OK"
if (req.status == 200) {
var profile = req.responseXML;
if (profile) {
// do stuff
}
else {
indicateInvalidUsername(req.__username);
}
}
}
}

Resources