[Howto] Language based rewrites with Lighttpd and mod_magnet

| No Comments | No TrackBacks

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

While migrating from our old blogging software to Movable Type we decided it would be a good idea 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.

We are using Lighttpd to serve all pages, which means that our best option was the mighty mod_magnet module. This allows you to control request handling within Lighttpd by running Lua scripts, which are able to modify most aspects of the way a request is handled. http://blog.credativ.com/ is now rewritten to the file in the correct language with the help of the following Lua snippet:

-- - 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 has to 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 asked for a script to parse the full Accepted-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.

No TrackBacks

TrackBack URL: http://blog.credativ.com/mt-tb.cgi/132

Leave a comment