At the Chemnitzer Linux Tage the fedora project was assimilated by the Debian project as fedora realized that rpm is crap ;)

Posted Sat 13 Mar 2010 05:12:06 PM CET Tags:

lighttpd is web server with a fast growing user base. This howto shows how redirects can be done based on the language of the user's browser.

While migrating http://blog.credativ.com from Wordpress to Movable Type we decided to show the blog's welcome message in English or German depending on the language setting of the user's browser. Since one of the reasons for the switch to the new blog engine was that Movable Type creates static html pages we avoided cgi scripts or similar workarounds.

As we are using Lighttpd to serve all pages, the use of the mighty mod_magnet module was obviously the way to go for this task. It allows to control the request handling within Lighttpd by running Lua scripts which are allowed to modify most aspects of the way how a request is handled. So now http://blog.credativ.com/ is rewritten to the file with the proper language by using the help of the following Lua snippet:

-- - do not forget to install liblua5.1-lpeg2
-- - make sure to configure the script here -----------------------------

language_targets = {}
language_targets["en"] = "/en/index.html"
language_targets["de"] = "/de/index.html"
default_language = "en"


-- - nothing to customize below this line -------------------------------
--

--[[ string:split function taken from http://lua-users.org/wiki/SplitJoin
      Thanks to Joan Ordinas
  ]]
function string:split(sSeparator, nMax, bRegexp)
    assert(sSeparator ~= '')
    assert(nMax == nil or nMax >= 1)

    local aRecord = {}

    if self:len() > 0 then
        local bPlain = not bRegexp
        nMax = nMax or -1

        local nField=1 nStart=1
        local nFirst,nLast = self:find(sSeparator, nStart, bPlain)
        while nFirst and nMax ~= 0 do
            aRecord[nField] = self:sub(nStart, nFirst-1)
            nField = nField+1
            nStart = nLast+1
            nFirst,nLast = self:find(sSeparator, nStart, bPlain)
            nMax = nMax-1
        end
        aRecord[nField] = self:sub(nStart)
    end

    return aRecord
end

-- Based on trim14 from http://lua-users.org/wiki/StringTrim
do
    require 're'
    require 'lpeg'

    local ptrim = re.compile"%s* {(%s* %S+)*}"
    local match = lpeg.match
    function string:trim()
        return match(ptrim, self)
    end
end


lang_header = lighty.request['Accept-Language']
lighty.env["uri.path"] = language_targets[default_language]
if (lang_header) then
    lang_header = string.lower(lang_header)
    local lang_order = {}
    for i, language in ipairs(string.split(lang_header, ",")) do
        language_configs = string.split(language, ";")
        language = string.trim(language_configs[1])
        table.remove(language_configs, 1)
        if     ((#language == 2) and string.find(language, "[a-z][a-z]"))
            or ((#language == 5) and string.find(language, "[a-z][a-z][-][a-z][a-z]"))
        then
            local q = 1
            for i, config in ipairs(language_configs) do
                local config_data = string.split(config, "=")
                if (#config_data == 2) then
                    local lvalue = string.trim(config_data[1])
                    local rvalue = string.trim(config_data[2])
                    if lvalue == "q" then
                        q = tonumber(rvalue)
                    end
                end
            end
            table.insert(lang_order, {language, q})
        end
    end
    table.sort(lang_order, function(a,b) return (a[2] > b[2]) end)
    for i,v in ipairs(lang_order) do
        local lang = string.split(v[1], '-')[1]
        if language_targets[lang] then
            lighty.env["uri.path"] = language_targets[lang]
            break
        end
    end
end

lighty.env["physical.rel-path"] = lighty.env["uri.path"]
lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]

Of course, the Lighttpd configuration must include mod_magnet. To actually rewrite any request to "/" the configuration must also include the following snippet:

$HTTP["url"] =~ "^/$" {
	magnet.attract-physical-path-to = ( "/path/to/your/script.lua" )
}

mod_magnet caches the compiled script and executes it within the core of Lighttpd so it shouldn't introduce any noticeable delay in the delivery of your webpages.

Update: Some people complained in comments (which I lost while moderating a ton of spam comments unfortunately) that the script did not parse the full Accept-Language header. Although this was not really an issue for us as the two blogs are independent and people have to check which blog to read in any case, I've updated the script to parse the header properly. Also it should be easier to re-use for your own needs now.

Posted Sat 13 Mar 2010 05:12:06 PM CET Tags: