A Quick and Dirty Reverse Proxy with PHP

I’m polishing off a new mapping site that uses Geoserver/Tilecache as a WMS back end. Tilecache is a Python WMS/TMS server that caches images to speed up your WMS client. It’s the bees knees.

Unless, like your intrepid scribe, you’re running IIS.

If all you want to run is ASP.NET apps, IIS is bloody brilliant.* If you want to serve up anything else, IIS eats it. Big time. IIS will only run Python in CGI mode, and Python in CGI mode is a dog. What I really needed was Apache with mod_python.

I have Apache as the primary web server on an internal server, but I needed a Tilecache folder on it accessible to the web. Punching a hole through the firewall was a no-go for a number of reasons, a few of which were arguably good.

What I did instead was build a simple reverse proxy using PHP.

A reverse proxy server is what it sounds like. Generally speaking, a proxy server services the requests of its clients by forwarding the requests to other servers. Instead of connecting directly to a web site, you connect to your proxy server that then connects to the website and relays the information. You do this for a lot of reasons - caching, security, anonymity, etc. A reverse proxy is a proxy in reverse. Rather than fielding your internal clients and connecting to external servers, it handles external client requests and passes them to internal servers. To the external client, it looks like they’re grabbing something off of your web-facing server, but it’s really coming from somewhere else.

Enough blather. Let’s look at some code.

First, set the error reporting level. I make it a habit to always set that in my projects, even though I’m generally the one setting the default in the php.ini.

error_reporting(E_ERROR);
Next, let's capture any incoming arguments to pass along to the back-end server.
$args = $_SERVER['QUERY_STRING'];
Now let's set up our reverse proxy. We're going to be using the CURL library, so if it isn't loaded in your php.ini file you'll need to load it (dl() or in the php.ini). We're also setting CURLOPT_RETURNTRANSFER because we want to capture the backend server output as a string.
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://internal_server/tilecache-1.9/tilecache.py?".$args); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
Next, we'll execute the curl object and load the result into $output.
$output = curl_exec($ch);
You could do all kinds of neat stuff now. $output is just a string, so you can use any of PHP's vast string manipulators. For example, if you were hitting an internal ArcIMS site, which uses absolute URL's for the map images, you could do a string replace to whatever your external server URL is.

Finally, send the $output to the browser and clean up after yourself. Note we’re passing the content type we got from the back end server on to the client. That’s important, particularly with mapping services that can kick out XML, images, etc.

header('Content-type: '.curl_getinfo($ch, CURLINFO_CONTENT_TYPE)); echo $output; curl_close($ch);
That's the whole thing. Now just drop it on to your web server, and you'll be able to hit the php file and it will seamlessly pass requests to and responses from your internal web server. This is an extremely simple reverse proxy, but it's useful, and I think it gives you an idea of the many things you can do with it.

Now that this code is hooked up, my Geoserver/Tilecache site is flying. I hope to have it ready to launch as a beta shortly.

Here’s all the code:

######################################################## ## Reverse Proxy for Tilecache ########################################################

// Set error level reporting level
error_reporting(E_ERROR);

// Capture URL Arguments
$args = $_SERVER[‘QUERY_STRING’];

// create a new curl resource and set options
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, “http://internal_server/tilecache-1.9/tilecache.py?".$args);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// grab URL, and return output
$output = curl_exec($ch);

// Send the header based on the response from the server
header(‘Content-type: ‘.curl_getinfo($ch, CURLINFO_CONTENT_TYPE));

// Send the curl output
echo $output;

// close curl resource
curl_close($ch);


*The script kiddies will think your decision to run IIS is bloody brilliant as well, but for different reasons.