I maintain a site with a bunch of downloadable files. It is currently hosted on a server in the U.S., but I have recently acquired a new server in Germany. I would like to mirror the downloads to the server in Germany, and have a PHP script on the first server (hosting the website) detect which file mirror to use based on the user's location. For instance, if the user is in Canada, they should download the file from my current server in the U.S. If they're in France, they should get the file from Germany, rather than downloading across the Atlantic. How, then, can I determine which country they are closer to?
I know about MaxMind GeoIP, and have it installed, but that just gives me a country, and AFAIK, there is no way to automatically determine which of my two mirror countries the given country is closest to. I suppose what I could do is go by continent: have users in Asia, Europe, Africa, and Australia get the content from Germany, and have visitors from North and South America get the file from the U.S. If anyone can think of a better solution, I'm open to suggestions.
Well, I guess I am going to go go with my original idea of checking by continents. For others looking to do this sort of thing, that will be a good place to start. The problem will come when I have multiple mirrors in Europe, but the continent idea will have to work for now.
Seems there is a lot of developer overhead in the proposed solutions thus far. If this was a problem I had to address in my own applications, I might save me a few hours of work by opting not to reinvent the wheel on this one.
Determining the Closest Mirror (Using Zip Codes)
Please keep in mind that a closer distance does not necessarily constitute a faster response time. In the context of your scenario, however, a mirror in one country will obviously be faster than a mirror in another, assuming both mirrors are up. Continue reading for what I consider to be a more "robust" solution.
Resources & Links
PHP 5 Zip Code Range and Distance Calculation: http://www.micahcarrick.com/php5-zip-code-range-and-distance.html
ip2nation Country Based Redirection: http://www.ip2nation.com/ip2nation/Sample_Scripts/Country_Based_Redirect
Might also be good to look for a library or MySQL dump that will help you derive the postal code or region of a given IP address (e.g. ip2nation). Example: How to determine a zip code and city from an IP address?
The "Maverick" Approach
In my opinion, Mavericks are also known as those innovators, problem solvers, and inventors of these great libraries and frameworks we all use today. Sometimes mistakenly associated with "hackish" ideas, but we embrace the complement :)
Create your own API service on either one of your mirror servers that will accept either a $_GET or $_POST request.
This API service will take an IP address it is given and ping() it, calculating the response times and then taking the average, returning it to the requesting interface (e.g. your frontend portal through which clients are connecting and/or the server trying to determine the closest mirror). The server that responds with the lowest average ought to be your quickest responding server, albeit not necessarily closest. Which is more important to you? See Ping site and return result in PHP for a working ping() function that does not rely on executing shell commands locally (e.g. platform independent).
Last step, derive the IP address of the requesting client and pass it to your API service running on either mirror server in the background. And we all know how to derive the IP, but not as well as you think we might. If you're load balanced or behind a proxy, you may want to first check to see if any of these headers came through (HTTP_FORWARDED, HTTP_FORWARDED_FOR, HTTP_X_FORWARDED, HTTP_X_FORWARDED_FOR, HTTP_CLIENT_IP). If so, that's probably the real IP address of the user agent.
It is at this point (Step 3) where you would compare the averages of response times that each mirror replied with when they went to ping the user agent. Then proceed with selecting which mirror the user agent should download from. The service flow you will have created then resembles something like this:
Hope that helps and happy coding!