Forward http proxy using node http proxy - node.js

I'm using the node-http-proxy library to create a forward proxy server.
I eventually plan to use some middleware to modify the html code on the fly.
This is how my proxy server code looks like
var httpProxy = require('http-proxy')
httpProxy.createServer(function(req, res, proxy) {
var urlObj = url.parse(req.url);
console.log("actually proxying requests")
req.headers.host = urlObj.host;
req.url = urlObj.path;
proxy.proxyRequest(req, res, {
host : urlObj.host,
port : 80,
enable : { xforward: true }
});
}).listen(9000, function () {
console.log("Waiting for requests...");
});
Now I modify chrome's proxy setting, and enable web proxy server address as localhost:9000
However every time I visit a normal http website, my server crashes saying "Error: Must provide a proper URL as target"
I am new at nodejs, and I don't entirely understand what I'm doing wrong here?

To use a dynamic target, you should create a regular HTTP server that uses a proxy instance for which you can set the target dynamically (based on the incoming request).
A bare bones forwarding proxy:
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({});
http.createServer(function(req, res) {
proxy.web(req, res, { target: req.url });
}).listen(9000, () => {
console.log("Waiting for requests...");
});

Related

node.js node-http-proxy: How to avoid exceptions with WebSocket when target in proxy.web

Using http-proxy (aka node-http-proxy) in node.js, I am having trouble figuring out how to proxy web sockets when the target is determined dynamically (i.e. when processing the request).
Looking at the documentation:
https://github.com/nodejitsu/node-http-proxy
There is an example that shows being able to set the target when processing the request:
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function(req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, { target: 'http://127.0.0.1:5060' });
});
console.log("listening on port 5050")
server.listen(5050);
There is another example farther down showing support for websockets via proxy.ws(), but it shows the target being set statically rather than depending on the request:
//
// Setup our server to proxy standard HTTP requests
//
var proxy = new httpProxy.createProxyServer({
target: {
host: 'localhost',
port: 9015
}
});
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) {
proxy.ws(req, socket, head);
});
proxyServer.listen(8015);
I took the first example and added the proxyServer.on('upgrade'... proxy.ws() ... stuff from the second example in order to get an example that sets the target while processing the request and also supports websockets. HTTP web pages seem to work fine, but it throws an exception when handling a websocket request.
'use strict';
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function(req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, { target: 'http://127.0.0.1:5060' });
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
server.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head);
});
console.log("listening on port 5050")
server.listen(5050);
The exception happens in the proxy.ws(req, socket, head) call:
Error: Must provide a proper URL as target
at ProxyServer.<anonymous> (...../node_modules/http-proxy/lib/http-proxy/index.js:68:35)
at Server.<anonymous> (...../poc.js:26:9) // the location in my sample code of the proxy.ws(req, socket, head) above
at emitThree (events.js:116:13)
at Server.emit (events.js:194:7)
at onParserExecuteCommon (_http_server.js:409:14)
at HTTPParser.onParserExecute (_http_server.js:377:5)
The code in http-proxy/index.js:68:35 throws this exception if there is no .target or .forward member of the options.
How do I set the target on a per request basis and also get websockets to work?
I have an answer. After looking at this question by Conrad and the comments, and then experimenting:
single-proxyserver-to-multiple-targets
proxy.ws can take an additional argument of options, just like proxy.web.
Here is the working code.
'use strict';
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function(req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, { target: 'http://127.0.0.1:5060' });
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
server.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head, { target: 'ws://127.0.0.1:5060' });
});
console.log("listening on port 5050")
server.listen(5050);

NodeJS HTTP proxy - Add a header and update the request URL

I am using this module to do the following:
Parse the req URL
Add a new header to the request using the token from the URL
Update the actual request URL (remove the token from the URL)
I am trying to do that with the following code:
function initializeServer(){
var server = app.listen(5050, function () {
var host = server.address().address
var port = server.address().port
logger.info('NodeJS Server listening at http://%s:%s', host, port)
});
}
proxy.on('proxyReq', function(proxyReq, req, res, options) {
console.log("intercepting ... ")
proxyReq.setHeader('x-replica', '123');
req.url = '/newurl';
});
function initializeController(){
app.get('/myapp*', function (req, res) {
proxy.web(req, res, { target: 'http://127.0.0.1:8081' });
});
}
where 8081 is my test server and proxy server runs at 5050.
Now, the header setting works but the setting the URL does not. How to achieve this with node HTTP proxy ?
In the proxy.on('proxyReq',...) handler req is the (original) incoming request, while proxyReq is the request that will be issued to the target server. You need to set the proxyReq.path field.

What's the difference between "http-proxy" and "request"?

I need to create forwarding proxy (not reverse proxy), there are two packages for Node.js http-proxy and request
I don't understand what's the difference between those in case of creating proxy? Are they doing exactly the same, or there are some tricky corner cases?
http-proxy
var http = require('http');
var proxy = require('http-proxy').createProxyServer();
http.createServer(function(req, res) {
proxy.web(req, res, {
target: "http://" + req.headers.host
});
}).listen(3000, 'localhost');
request
var http = require('http');
var request = require('request');
http.createServer(function(req, res) {
req.pipe(request(req.url)).pipe(res);
}).listen(3000, 'localhost');
The two examples you've given are functionally the same, though I would still prefer http-proxy, as it already comes with some assumptions that you are specifically creating reverse/forward proxy requests.

How to redirect requests / proxy based on User-Agent (mobile and desktop) using Grunt or Node server?

I'm using Grunt and its proxying library grunt-connect-proxy. I have two servers setup for the desktop and mobile versions of my site (both of which have separate assets and such, hence the separation). Both sites are hosted on 0.0.0.0 but on different ports (9000 and 10000).
How can I proxy requests to the two different servers based on the User-Agent header (which will tell me if it is a mobile or desktop user)? Is there another solution in NodeJS I could use?
I ended up writing a NodeJS server which used the http-proxy and mobile-detect packages to proxy requests.
var servers = {
desktopClientFrontend: 'http://0.0.0.0:10000',
mobileClientFrontend: 'http://0.0.0.0:9000',
railsApiBackend: 'http://0.0.0.0:11000'
};
var http = require('http'),
url = require('url'),
httpProxy = require('http-proxy'),
MobileDetect = require('mobile-detect');
var proxy = httpProxy.createProxyServer({});
proxy.on('error', function (err, req, res) {
res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end('Something went wrong. And we are reporting a custom error message.');
});
var server = http.createServer(function(req, res) {
if(url.parse(req.url).pathname.match(/^\/api\//)) {
proxy.web(req, res, { target: servers.railsApiBackend });
} else {
var mobileDetect = new MobileDetect(req.headers['user-agent']);
if(mobileDetect.mobile()) {
proxy.web(req, res, { target: servers.mobileClientFrontend });
} else {
proxy.web(req, res, { target: servers.desktopClientFrontend });
}
}
});
server.listen(80);

creating a forward https proxy using http-node-proxy

I am trying to create a forward proxy capable of handling HTTPS websites as well. I am trying to observe and modify traffic for different sites. This is my code which works for http sites but not for https sites.
httpProxy.createServer(function(req, res, next) {
//custom logic
next();
}, function(req, res) {
var proxy = new httpProxy.RoutingProxy();
var buffer = httpProxy.buffer(req);
var urlObj = url.parse(req.url);
req.headers.host = urlObj.host;
req.url = urlObj.path;
console.log(urlObj.protocol);
setTimeout(function() {
proxy.proxyRequest(req, res, {
host: urlObj.host,
port: 80,
buffer: buffer,
}
)}, 5000);
}).listen(9000, function() {
console.log("Waiting for requests...");
});
Thanks for your help guys!
There are https options which must be specified when handling the https traffic. Here is what I am doing in my proxy setup.
var fs = require('fs'),
httpProxy = require('http-proxy');
var proxyTable = {};
proxyTable['domain.com'] = 'localhost:3001';
proxyTable['domain.com/path'] = 'localhost:3002';
proxyTable['sub.domain.com'] = 'localhost:3003';
var httpOptions = {
router: proxyTable
};
var httpsOptions = {
router: proxyTable,
https: {
passphrase: 'xxxxxxx',
key: fs.readFileSync('/path/to/key'),
ca: fs.readFileSync('/path/to/ca'),
cert: fs.readFileSync('/path/to/crt')}
};
httpProxy.createServer(httpOptions).listen(80);
httpProxy.createServer(httpsOptions).listen(443);
The documentation for https is on their github page as well.
https://github.com/nodejitsu/node-http-proxy
If you're just doing a forward proxy there's a few things you'll have to take into account.
A regular request is NOT triggered on a proxy for a HTTPS request - instead you'll see a HTTP CONNECT.
Here's the sequence flow you'll need to handle.
CONNECT event is sent from the browser to the proxy specified in the HTTPS section. You'll catch this here: http://nodejs.org/api/http.html#http_event_connect Note that this comes over the HTTP module, not the HTTPS connection.
You create a new socket connection to the requested domain (or your mapped domain). [srvSocket]
You'll respond back to the CONNECT socket with a 200
You'll write the buffer you received with the CONNECT event to srvSocket, then pipe the two sockets together srvSocket.pipe(socket);
socket.pipe(srvSocket);
Since you're trying to spoof the requested domain locally you'll need a few more things in place
You'll need to generate a root CA.
You will need to import this cert as a trusted authority to your OS
You'll use this cert to create a new key/cert file for the domains you're trying to access
Your mapped hosts will need to respond with the appropriate key/cert file generated in step 3 for EACH domain you are mapping.
https://github.com/substack/bouncy
var bouncy = require('bouncy');
var server = bouncy(function (req, res, bounce) {
if (req.headers.host === 'beep.example.com') {
bounce(8001);
}
else if (req.headers.host === 'boop.example.com') {
bounce(8002);
}
else {
res.statusCode = 404;
res.end('no such host');
}
});
server.listen(8000);
If you specify opts.key and opts.cert, the connection will be set to secure mode using tls. Do this if you want to make an https router.
We can have a middleware as below
request = require("request");
app.use(function (req, res, next) {
request('http://anotherurl.that.serves/the/request').pipe(res);
});
See example https://github.com/manuks/proxy
Basically, underneath the http-proxy npm is some networking libraries Node uses (specifically http://nodejs.org/api/https.html and TLS). Even though my Apache was able to connect me just fine on a self-signed certificate w/o the proxy by accessing it in my browser:
https://localhost:8002
You need to establish a certificate authority to get past the "unable to verify leaf signature" error in Node (I used the SSLCACertificateFile option). Then, you'll get hit with "self_signed_cert_in_chain". This led to some Google results indicating npm abandoned self-signed certificates, but I'm pretty sure this does not regard Node.
What you end up with are some people indicating you use process.env.NODE_TLS_REJECT_UNAUTHORIZED or rejectUnauthorized within your https agent. If you dig through the http-proxy souce, you'll find it accepts an agent option. Use this:
/**
* Module dependencies
*/
// basic includes
express = require('express');
fs = require('fs');
http = require('http');
https = require('https');
httpProxy = require('http-proxy');
require('child_process').spawn(__dirname+'/../../../dependencies/apache/bin/httpd.exe',['-f',__dirname+'/../../../dependencies/apache/conf/httpd-payments.conf']);
var app = module.exports = express();
app.set('port', process.env.PORT || 8001); // we sometimes change the port
// creates an output object for this particular request
//app.use(express.cookieParser(''));
//app.use(express.bodyParser());
//app.use(express.methodOverride());
proxy = httpProxy.createProxyServer();
proxy.on('error', function (err, req, res) {
console.log(err);
res.send(500,err);
res.end();
});
app.all('*',function(req,res,next) {
var options = {
hostname: '127.0.0.1',
port: 8002,
rejectUnauthorized: false,
key: fs.readFileSync(__dirname+"/../../../deployment/server.key.pem"),
cert: fs.readFileSync(__dirname+"/../../../deployment/server.crt.pem")
};
agent = new https.Agent(options);
try {
proxy.web(req,res, {
target: "https://localhost:8002",
proxyTimeout: 30,
agent: agent
});
} catch(e) {
// 500 error
res.send(500,e);
}
})
/**
* Start Server
*/
var options = {
key: fs.readFileSync(__dirname+"/../../../deployment/server.key.pem"),
cert: fs.readFileSync(__dirname+"/../../../deployment/server.crt.pem")
};
server = https.createServer(options,app).listen(app.get('port'), function () {
console.log('Running payments server on port ' + app.get('port'));
});

Resources