Lighttpd
上QQ阅读APP看书,第一时间看更新

O Browser, Where Art Thou?

Early methods to find out where users are resident involved a reverse DNS lookup and use of the Top-Level-Domain (which in many but not all cases were country domains) to find the country. These methods worked sometimes. They, however, broke down when a visitor came from a .com, .org, .gov, .edu, .info domain or any other non-country domain. Plus, some providers now use a .tv domain just for the heck of it, placing all their customers in Tuvalu on our radar.

With mod_geoip, we can attach a Geo-IP-database to Lighttpd to find out where our visitors are based directly from their IP address. This works much better and does not need a DNS lookup (which is quite time-consuming, involving yet another network call, with the likelihood of failure at times).

mod_geoip is not part of the standard distribution of Lighttpd. We have to download and install it manually. If we use Lighttpd version 1.5, we have to get the file http://trac.lighttpd.net/trac/attachment/wiki/Docs/ModGeoip/mod_geoip.4.c; for earlier versions, get http://trac.lighttpd.net/trac/attachment/wiki/Docs/ModGeoip/mod_geoip.5.c. Put this file in the src directory of the unpacked Lighttpd source.

Also, we need the GeoIP C Api and a database. We can get both these from the GeoIP database website. Regarding the database, we have to decide between a 570 kilobyte-sized database containing only IP and country information, and a 17.2 megabyte database that includes city information (both gzipped). Here is a list of links:

The C API needs to be compiled and installed, too. Unzip the GeoIP.tar.gz file and perform the usual installation steps (the following command line example assumes GeoIP version 1.4.5):

$ tar xzf GeoIP.tar.gz $ cd GeoIP-1.4.5 $ ./configure && make && make install checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking whether make sets $(MAKE)... yes ... (more messages from configure and make omitted for brevity) ...

We may also want to put the database into some directory, which will later be included in our Lighttpd configuration, for example, /opt/geoip/. Do not forget to gunzip the database file.

As the installation routine for mod_geoip is not included in the standard Lighttpd source distribution, we need to add it to the automake definitions. This is done by adding the following to src/Makefile.am at the end of the file:

lib_LTLIBRARIES += mod_geoip.la
mod_geoip_la_SOURCES = mod_geoip.c
mod_geoip_la_LDFLAGS = -module -E -avoid-version -no-undefined
mod_geoip_la_LIBADD = $(common_libadd) -lGeoIP

Then we need to go into the Lighttpd source directory and rebuild the configure script. Finally, we can configure, compile, and install our Lighttpd. For the configure script, we need to enable the maintainer mode, so automake will find mod_geoip.

$ aclocal && automake -a && autoconf $ ./configure enable-maintainer-mode checking build system type... i686-pc-linux-gnu checking host system type... i686-pc-linux-gnu checking target system type... i686-pc-linux-gnu checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes ... (more messages from configure omitted for brevity) ... $ make && make install cd . && /bin/sh /home/andre/lighttpd-1.4.19/missing --run autoheader rm -f stamp-h1 touch config.h.in cd . && /bin/sh ./config.status config.h config.status: creating config.h make all-recursive ... (a lot of messages from make omitted for brevity) ...

Now that we have installed mod_geoip, we can use it to resolve IP addresses into location information, which is then placed in the environment. Add mod_geoip to our server.modules and set the path to our database (mod_geoip will figure out autonomously which type of database we feed it):

server.modules += ("mod_geoip")
geoip.db-filename = "/opt/geoip/GeoLiteCity.dat"

This should be enough to get some new request headers in our environment. The whole configuration of mod_geoip is as follows:

Configure mod_geoip and restart Lighttpd. The following request parameters will be set based on the client IP address:

For more information on the ISO 3166 country codes, see the discussion at http://en.wikipedia.org/wiki/ISO_3166-1.

As of Lighttpd version 1.5, the configuration is in the global scope, otherwise, Lighttpd may hang. Also the request parameters registered by mod_geoip cannot be used in configuration selectors. However, we can use a mod_magnet script or a CGI backend program to make use of the information. For example, here is a mod_magnet script that redirects based on country code:

-- redir-country.lua mod_magnet script
-- redirect-by-country, default to "us"
local countryCode = string.lower(
lighty.env("GEOIP_COUNTRY_CODE") or "us")
lighty.header["Location"] = string.gsub(
lighty.env["request.uri"] or "",
"^(www%.)?", countryCode .. ".")
return 302

Note that this magnet script should only attract connections to ourdomain.com or www.ourdomain.com. This can be ensured by placing the attractor in a selector on host:

server.modules = (..., "mod_magnet", ...) # add mod_magnet
# match only ourdomain.com and www.ourdomain.com
$HTTP["host"] =~ "^(www\.)?ourdomain.com" {
magnet.attract-raw-url-to = "/www/magnetscripts/redir-country.lua"
} else $HTTP["host"] =~ "(\w\w)\.ourdomain.com" {
url.rewrite = ("^(.*)$" => "/%1/$1")
}

The else part is mapping the country-code domains to subdirectories of the document root. Now all we have to do is create the directories for each country code containing everything we want. Alternatively, we could use a web application to match the country codes to languages.