var http = require('http');

http.createServer(function(request, response) {
  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.addListener('response', function (proxy_response) {
    proxy_response.addListener('data', function(chunk) {
      response.write(chunk, 'binary');
    });
    proxy_response.addListener('end', function() {
      response.end();
    });
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });
  request.addListener('data', function(chunk) {
    proxy_request.write(chunk, 'binary');
  });
  request.addListener('end', function() {
    proxy_request.end();
  });
}).listen(8080);

This is just amazing. In 20 lines of node.js code and 10 minutes of time I was able to write a HTTP proxy. And it scales well, too. It's not a blocking HTTP proxy, it's event driven and asynchronous, meaning hundreds of people can use simultaneously and it will work well.

To get the proxy running all you have to do is download node.js, compile it, and run the proxy program via the node program:

$ ./configure --prefix=/home/pkrumins/installs/nodejs-0.1.92
$ make
$ make install

$ PATH=$PATH:/home/pkrumins/installs/nodejs-0.1.92/bin

$ node proxy.js

And from here you can take this proxy wherever your imagination takes. For example, you can start by adding logging:

var http = require('http');
var sys  = require('sys');

http.createServer(function(request, response) {
  sys.log(request.connection.remoteAddress + ": " + request.method + " " + request.url);
  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.addListener('response', function (proxy_response) {
    proxy_response.addListener('data', function(chunk) {
      response.write(chunk, 'binary');
    });
    proxy_response.addListener('end', function() {
      response.end();
    });
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });
  request.addListener('data', function(chunk) {
    proxy_request.write(chunk, 'binary');
  });
  request.addListener('end', function() {
    proxy_request.end();
  });
}).listen(8080);

Next, you can add a regex-based host blacklist in 15 additional lines:

var http = require('http');
var sys  = require('sys');
var fs   = require('fs');

var blacklist = [];

fs.watchFile('./blacklist', function(c,p) { update_blacklist(); });

function update_blacklist() {
  sys.log("Updating blacklist.");
  blacklist = fs.readFileSync('./blacklist').split('\n')
              .filter(function(rx) { return rx.length })
              .map(function(rx) { return RegExp(rx) });
}

http.createServer(function(request, response) {
  for (i in blacklist) {
    if (blacklist[i].test(request.url)) {
      sys.log("Denied: " + request.method + " " + request.url);
      response.end();
      return;
    }
  }

  sys.log(request.connection.remoteAddress + ": " + request.method + " " + request.url);
  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.addListener('response', function(proxy_response) {
    proxy_response.addListener('data', function(chunk) {
      response.write(chunk, 'binary');
    });
    proxy_response.addListener('end', function() {
      response.end();
    });
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });
  request.addListener('data', function(chunk) {
    proxy_request.write(chunk, 'binary);
  });
  request.addListener('end', function() {
    proxy_request.end();
  });
}).listen(8080);

update_blacklist();

Now to block proxy users from using Facebook, just echo facebook.com to blacklist file:

$ echo 'facebook.com' >> blacklist

The proxy server will automatically notice the changes to the file and update the blacklist.

Surely, a proxy server without IP control is no proxy server, so let's add that as well:

var http = require('http');
var sys  = require('sys');
var fs   = require('fs');

var blacklist = [];
var iplist    = [];

fs.watchFile('./blacklist', function(c,p) { update_blacklist(); });
fs.watchFile('./iplist', function(c,p) { update_iplist(); });

function update_blacklist() {
  sys.log("Updating blacklist.");
  blacklist = fs.readFileSync('./blacklist').split('\n')
              .filter(function(rx) { return rx.length })
              .map(function(rx) { return RegExp(rx) });
}

function update_iplist() {
  sys.log("Updating iplist.");
  iplist = fs.readFileSync('./iplist').split('\n')
           .filter(function(ip) { return ip.length });
}

http.createServer(function(request, response) {
  var allowed_ip = false;
  for (i in iplist) {
    if (iplist[i] == request.connection.remoteAddress) {
      allowed_ip = true;
      break;
    }
  }

  if (!allowed_ip) {
    sys.log("IP " + request.connection.remoteAddress + " is not allowed");
    response.end();
    return;
  }

  for (i in blacklist) {
    if (blacklist[i].test(request.url)) {
      sys.log("Denied: " + request.method + " " + request.url);
      response.end();
      return;
    }
  }

  sys.log(request.connection.remoteAddress + ": " + request.method + " " + request.url);
  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.addListener('response', function(proxy_response) {
    proxy_response.addListener('data', function(chunk) {
      response.write(chunk, 'binary');
    });
    proxy_response.addListener('end', function() {
      response.end();
    });
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });
  request.addListener('data', function(chunk) {
    proxy_request.write(chunk, 'binary');
  });
  request.addListener('end', function() {
    proxy_request.end();
  });
}).listen(8080);

update_blacklist();
update_iplist();

By default the proxy server will not allow any connections, so add all the IPs you want the proxy to be accessible from to iplist file:

$ echo '1.2.3.4' >> iplist

Finally, let's refactor the code a little:

var http = require('http');
var sys  = require('sys');
var fs   = require('fs');

var blacklist = [];
var iplist    = [];

fs.watchFile('./blacklist', function(c,p) { update_blacklist(); });
fs.watchFile('./iplist', function(c,p) { update_iplist(); });

function update_blacklist() {
  sys.log("Updating blacklist.");
  blacklist = fs.readFileSync('./blacklist').split('\n')
              .filter(function(rx) { return rx.length })
              .map(function(rx) { return RegExp(rx) });
}

function update_iplist() {
  sys.log("Updating iplist.");
  iplist = fs.readFileSync('./iplist').split('\n')
           .filter(function(rx) { return rx.length });
}

function ip_allowed(ip) {
  for (i in iplist) {
    if (iplist[i] == ip) {
      return true;
    }
  }
  return false;
}

function host_allowed(host) {
  for (i in blacklist) {
    if (blacklist[i].test(host)) {
      return false;
    }
  }
  return true;
}

function deny(response, msg) {
  response.writeHead(401);
  response.write(msg);
  response.end();
}

http.createServer(function(request, response) {
  var ip = request.connection.remoteAddress;
  if (!ip_allowed(ip)) {
    msg = "IP " + ip + " is not allowed to use this proxy";
    deny(response, msg);
    sys.log(msg);
    return;
  }

  if (!host_allowed(request.url)) {
    msg = "Host " + request.url + " has been denied by proxy configuration";
    deny(response, msg);
    sys.log(msg);
    return;
  }

  sys.log(ip + ": " + request.method + " " + request.url);
  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.addListener('response', function(proxy_response) {
    proxy_response.addListener('data', function(chunk) {
      response.write(chunk, 'binary');
    });
    proxy_response.addListener('end', function() {
      response.end();
    });
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });
  request.addListener('data', function(chunk) {
    proxy_request.write(chunk, 'binary);
  });
  request.addListener('end', function() {
    proxy_request.end();
  });
}).listen(8080);

update_blacklist();
update_iplist();

Again, it's amazing how fast you can write server software in node.js and JavaScript. It would have probably taken me a day to write the same in C. I love how fast you can prototype the software nowadays.

Download proxy.js

Download link: proxy server written in node.js
Download URL: http://www.catonmat.net/download/proxy.js
Downloaded: 14055 times

I am gonna build this proxy up, so I also put it on GitHub: proxy.js on GitHub

Happy proxying!

Comments

questions Permalink
April 28, 2010, 11:50

i have no clue about js event model.
so,
does the order in which proxy_response 'data' and 'end' events are received guarantee the order in which the respective callbacks are processed?

April 28, 2010, 15:20

Well events get queued. If there was 'data', then 'data' callback gets called. If the connection closes 'end' gets called. So they are in order.

April 28, 2010, 12:02

Glad you're following node.js as well.

April 28, 2010, 12:21

Great code, well done.
Maybe, we can think of something for my www.proxy.ps domain with this? Just in case.. Best wishes, Ruslan

April 28, 2010, 15:21

You could! With a bit of hacking and improvements. JavaScript is not that difficult!

April 28, 2010, 14:15

//var proxy = http.createClient(80, request.headers['host'])

This line means that the proxy is only HTTP1.1 compatible

April 28, 2010, 15:21

Well there is HTTP 1.0 and HTTP 1.1. What do you mean then by only?

April 28, 2010, 17:28

Why port 80 only? some webservers run on alternate ports.

April 29, 2010, 14:09

Port 80 because I wanted the code to fit in 20 lines.

You can just split on ':' on request.url and see if another port is specified...

April 30, 2010, 07:33

Header 'host' present in http-headers since v1.1 You must parse URL in first-line of http request.

April 30, 2010, 15:48

I am just forwarding all headers, the 'host' is in there!

prime Permalink
April 28, 2010, 16:33

It is as simple as using tornado, gevents, etc... But the problem is still one in most async solutions, if you need to block in one one call, all other connections will block. I am not talking about a socket block here though, but more of an intensive computation kind of block.
Erlang has a different solution for that.

April 29, 2010, 14:07

What's the Erlang's solution?

August 05, 2011, 22:13

I wouldn't say "Erlang has a solution" as this implies that it had the problem in the first place, which it didn't. Erlang's processes are concurrent, so there is no blocking in the same way that Node.js blocks. One process per request, one does not block on another unless you deliberately put something in place to make it happen.

April 28, 2010, 19:32

Very handy bit of code there. A useful little weight forwarding proxy

April 29, 2010, 02:05

that is so small! very useful!
i guess we can make that in python with simplehttpserver. that would be smaller. or not?

April 29, 2010, 14:08

It would be smaller but it also would be blocking. :(

April 29, 2010, 16:26

You could do it using the Twisted Python framework in a three lines or so (Twisted is asynchronous and event driven like nodejs) :)

April 29, 2010, 17:11

Can you show me how to do it in 3 loc of Twisted?

May 06, 2010, 19:41

from twisted.web import proxy, http
from twisted.internet import reactor

class ProxyFactory(http.HTTPFactory):
protocol = proxy.Proxy

reactor.listenTCP(8080, ProxyFactory())
reactor.run()

jherber Permalink
October 20, 2010, 02:18

Bah, this is pre-packaged class, not the actual proxy implemented in Twisted.

tom Permalink
December 28, 2011, 03:39

Um, what did you think the node.js one was? Did you not see the require('http') and require('sys') at the top?

Alexei Colin Permalink
July 13, 2013, 23:42

This twisted proxy supports HTTP 1.0 only.
They added a HTTP11ClientProtocol [1] in Twisted, but building a proxy on top of that would require a bunch more lines.

[1] http://twistedmatrix.com/documents/current/api/twisted.web._newclient.HTTP11ClientProtocol.html

Tom Permalink
April 30, 2010, 11:45

does it support https ?

April 30, 2010, 12:07

Sorry, it does not.

November 06, 2012, 01:50

Hello!

I've tried to write http proxy whichi can handle https in node.js.

source is here:
https://github.com/kjunichi/wsproxy/blob/master/myproxy0.js

Alomost it's work fine, but It's have some problem.

Yes,It's 200 lines over.

Thanks.

f00li5h Permalink
May 24, 2010, 12:46

meow!

seeker Permalink
July 25, 2010, 03:55

I am trying to do a slight improvement to the proxy, I would like to block sites if they contain certain words, the problem I am facing is that some sites are encoding their content via gzip. I am trying to use node-compress to decompress the stream, but was unsuccessful. If you've got an ideas or if you can add another example that would be greatly appreciated.

September 25, 2012, 02:38

The "easy" solution would be to strip any "Accept-Encoding" headers from incoming requests before forwarding them.

March 14, 2011, 17:21

Thanks for your nice proxy - we were quite inspired by your idea and wrote a translucent intercepting proxy (tip.js), and we're using it to modify the response of our web-service:

Translucent Intercepting Proxy

sami Permalink
June 10, 2011, 10:20

read file function should be updated:

fs.readFileSync('whitelist.txt', "utf8")

otherwise this returns an error: Object

<filename>

has no method 'split'

Apparently node core has been modified: https://github.com/joyent/node/issues/186

July 18, 2011, 01:10

That's really cool -- node is great for this sort of thing.

However, HTTP proxies aren't *quite* that simple; for example, you really need to strip hop-by-hop headers, or it'll break some things.

See what proxies must do.

July 25, 2011, 00:45

This works, but not on a lot of YouTube pages. I seems like YouTube rejects web requests that go through a proxy server and redirects you to a page not found page. I tried it using EventMachine in Ruby too, and the same result. Any ideas?

respectTheCode Permalink
September 17, 2011, 13:02

You can actually take 4 lines of code out by passing the end function directly to the end event listener. For example

request.addListener('end', function() {
  proxy_request.end();
});

can be written as

request.addListener('end', proxy_request.end);
iRespectTheCode Permalink
July 05, 2013, 14:59

No, I think this won't work:
Peter's code passes proxy_request as this to proxy_request.end whereas yours drops the context. I guess it would crash.

giuseppe Permalink
September 24, 2011, 14:06

You proxy fails for this video server (IExplorer)
http://87.22.235.24/index.htm
Regards and thanks for any tip

mobeen Permalink
October 07, 2011, 05:32

hi,
I want to create a proxy on the web server. I pass the url of the image I want to a specific route on my server and then it downloads the image data and returns it to my javascript thus hiding its true origins.All i want to deal with my localhost only.The actual web server remain hide
How i can done this please help
thanks

danny Permalink
February 14, 2012, 18:08

Try to use this proxy code, most of the Web sites can be loaded but I am wondering why it can't fully loading this site ->

http://en.wikipedia.org/wiki/List_of_HTTP_header_fields

For this site, I am getting lot of "404 Not Found" shown in Firebug.

Anyone, pls help.

Thanks.

April 12, 2012, 11:42

See this Q&A on Stackoverflow:
http://stackoverflow.com/questions/5924490/http-1-1-request-line

I made some quick tests and Wikipedia was the only site where I noticed this issue.

kumar Permalink
February 17, 2012, 14:30

Hi every body .I need a python code for sending and getting response from the server automatically .If you people will help ,It would be beter for me.

Eric Stob Permalink
February 24, 2012, 19:22

Coffee version = 11 lines

http = require 'http'
http.createServer (request, response) ->
  proxy = http.createClient 80, request.headers['host']
  proxy_request = proxy.request request.method, request.url, request.headers
  proxy_request.addListener 'response', (proxy_response) ->
    proxy_response.addListener 'data', (chunk) -> response.write chunk, 'binary'
    proxy_response.addListener 'end', response.end
    response.writeHead proxy_response.statusCode, proxy_response.headers;
  request.addListener 'data', (chunk) -> proxy_request.write chunk, 'binary'
  request.addListener 'end', proxy_request.end
.listen 8080
AndDM Permalink
March 01, 2012, 17:14

Hi, i saw that http.createClient is deprecated and the documentation suggest http.request to substitute it. What do you think about that? Maybe can you rewrite your code?

Thanks, regards.

July 21, 2012, 17:18

Here is adapted version for the newes NodeJS: https://gist.github.com/3156463

helder Permalink
March 05, 2012, 17:39

Hello,

thanks for your code.
I start working with your code to make a nodejs proxy, but with lots of changes already, but your code was a very good base.

I'm having a problem controlling DNS errors when the request domain is not available or does not exists, can you tip me about how can i catch the DNS error ?

george Permalink
March 14, 2012, 15:21

I ran the entire code and I get an error message which says Object 'facebook.com' has no method split. What does that mean? Sorry, i'm new to proxies

May 30, 2012, 17:19

If you're looking for something a bit simpler, available as a package check out: http://search.npmjs.org/#/realsimpleserver - works great for me.

Marco Permalink
July 20, 2012, 09:59

Hi, i don't know how to call the proxy for retrieve the response.
I always get a proxy loop message.

I need to call the proxy with jquery and retrieve the body in a jsonp response.

Or if is not possible to call with another http.get nodejs request.

Thanks

July 21, 2012, 17:28

Try something like this one:

proxy_response.on('data', function (chunk) {
console.log(chunck.toString()
}

Marco Permalink
July 23, 2012, 21:14

Thanks Ivan, but my problem is that i don't know how to pass the url parameter to the proxy.
I've tried with:
http://myproxy:myport/url=urltoget.com
http://myproxy:myport/u=urltoget.com
http://myproxy:myport/?url=urltoget.com
http://myproxy:myport/?u=urltoget.com
but i receive always the same message "Proxy loop!"

Elmer Bulthuis Permalink
September 25, 2012, 15:54

i proxy like this:

function serveProxy(host, port, url, req, res){
	var proxy = http.createClient(port, host);
	var proxy_req = proxy.request(req.method, url, req.headers);
	proxy_req.addListener('response', function (proxy_res) {
		res.writeHead(proxy_res.statusCode, proxy_res.headers);
		proxy_res.pipe(res);
	});
	req.pipe(proxy_req);
}

a little shorter!

Marc Permalink
October 21, 2012, 07:03

Would you mind knocking together a simple *reverse* proxy with node? Everyone seems to recommend using one of the modules (like http-proxy or bounce) but surely a simple reverse proxy (which listens on localhost:80 and forwards to google.com:80) is a trivial excercise. Googling so far has produced nothing!

Marc Permalink
October 22, 2012, 13:39

Aha, just changing this line:
var proxy = http.createClient(80, request.headers['host'])
to
var proxy = http.createClient(80, 'google.com')
makes it a reverse proxy instead of a normal HTTP proxy.

Leonardo Veríssimo Permalink
October 25, 2012, 13:09

This proxy is using an old version of Node. Today, I needed a simple proxy, like yours, and using the latest version (0.8.12), a new proxy could be written this way:

var http = require('http');

http.createServer(function(request, response) {
	var proxyRequest = http.request({
		host: request.headers['host'],
		port: 80,
		path: request.url,
		method: request.method,
		headers: request.headers
	}, function(proxyResponse) {
		response.writeHead(proxyResponse.statusCode, proxyResponse.headers);
		proxyResponse.pipe(response);
	});
	request.pipe(proxyRequest);	
}).listen(8080);

Just 14 lines!

Daniel Permalink
November 20, 2012, 07:18

I created a version compatible with node.js v0.84.
My version can modify the html-content and optimize page-load times.

https://github.com/ayurmedia/proxy.git

vin obieze Permalink
January 15, 2013, 08:29

hello please am looking for someone to write a proxy software for me with user authentication system so that i register people and gv them their id before they can use the proxy software.Please mail me on dzzl_dizzle@yahoo.com if you can lets talk.Is a project for my school work please.

pieter joubert Permalink
March 29, 2013, 07:46

Hi,
If you watch the first part of this video http://www.youtube.com/watch?v=hzyz3u3dlTg
it talks all about how the http module progressed (with the help of the guy in the video) and how writing a proxy in node changed from a 2 page program to eventually a 1 line (without requires and console loging) program

var http = require('http'),
request = require('request');
http.createServer(function(req,res){
req.pipe(request(req.url)).pipe(res)
}).listen(8000)
console.log("Server running at http://127.0.0.1:8000/")

Doo Dah Permalink
December 25, 2013, 05:56

Very nice. Thanks for the excellent tip!

Marty Permalink
May 30, 2013, 03:55

you can test proxies from http://www.proxybridge.com
It's free.

mike belshe Permalink
November 09, 2013, 00:54

This was a nice article once, but it is so out of date it would be nice if you just delete it before people waste their time trying to figure out why the apis you cite are no longer available or working....

A Alyan Permalink
May 08, 2014, 11:09

Can you inject headers in the response?

Koka Permalink
August 27, 2014, 10:33

After i set up proxy, I get the following error :

{ [Error: getaddrinfo ENOTFOUND] code: 'ENOTFOUND', errno: 'ENOTFOUND', syscall: 'getaddrinfo' }

whenever i access a localhost port. eg http://localhost:8080

September 11, 2014, 01:06

Thanks Bro Really I enjoyed . Thanks for Sharing :)
Snapdeal Coupons for memory Cards
Thanks

Leave a new comment

(why do I need your e-mail?)

(Your twitter name, if you have one. (I'm @pkrumins, btw.))

Type the word "lcd": (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.

Advertisements