By Tim Schürmann

The Lua scripting language was developed in 1993 at the University of Rio de Janeiro and was placed under the MIT license as of version 5. The interpreter, which weighs in at just a few kilobytes, is seriously fast and can use a C interface to dock with other programs. This has made Lua very popular in the gaming industry in particular. But Lua has slowly made inroads into more serious applications, such as Adobe’s Lightroom or Wireshark. Lua itself is an imperative and functional language, with rudimentary support for object-oriented programming. The syntax uses a minimum of keywords, which – on the downside – makes Lua programs more cryptic than their PHP counterparts.

Damp Squib

Thanks to all these properties, Lua would seem to be an obvious choice of scripting language for web servers. Thus, it is no surprise that modules for Apache have been presented at various phases of the language’s history. The ambitious Kepler project took this one step further, introducing a number of self-developed tools, libraries, and standards that aimed to give developers the ability to produce complete web applications simply in Lua. One of the results was Xavante, a web server written in Lua. The Kepler project has been dormant since 2010; the many Apache modules never made the cut for the official Apache package and thus quickly disappeared from sight – with one exception.

In 2006, Brian McCallister started work on a module with the slightly wacky name of mod_wombat . It welds the Lua interpreter onto the Apache web server, which lets the server launch and execute Lua scripts – very much in the style of mod_php and mod_perl . Although development was slow over the years, McCallister’s module officially made its way into the new Apache 2.4. Somewhere down the line, the developers renamed the module to mod_lua . Unfortunately, this is the name that many obsolete Apache modules also used.

If you look for documentation on the web, you are likely to stumble across much unusable ballast. Although mod_lua is part of the Apache package, it is still tagged as experimental; thus, it is not suitable for production use. That said, mod_lua is stable, and it allows programmers to work with hooks – and that makes it well worth looking into.

Moon Landing

If you want to use mod_lua , you need to create the module when you build the Apache server. To do this, you need version 5.1 of the Lua library, including the matching developer files. On Ubuntu, the packages go by the names of lua5.1 and liblua5.1.0-dev ; openSUSE users will need lua-devel and liblua5_1 .

Once you have Lua in place on your disk, you can turn to the Apache web server source code and run the provided configure with the additional --enable-lua parameter. Alternatively, you can append lua to --enable-mods-shared= , or just go for --enable-mods-shared=all .

If you are inexperienced with the build process but want to try out mod_lua in a small test environment on your own Linux PC, check out the “Quick Install” box for a brief how-to. The future v1.8.0 of the XAMPP package for this kind of application will include Apache 2.4, but the beta 2 version of mod_lua , which was the latest when this issue went to press, was strangely only available in the Windows variant.

To enable the Lua module, add the following line to your httpd.conf file (if you followed the instructions in the “Quick Install” box, you will find the file in ~/httpd/conf ):

LoadModule lua_module modules/mod_lua

To tell Apache to run any files with a .lua suffix as Lua scripts, you can add the following line:

AddHandler lua-script .lua

lua-script is a handler provided by mod_lua , and you can use it like any other Apache handler.

After storing the modified configuration, you need to restart the Apache server; then, you can proceed to write your own Lua scripts.

Suppliers

Any Lua script launched by Apache must have a handle() function. mod_lua calls this function and passes in a request object, typically called r :

function handle(r) ... do something ... end

The request object provides a couple of methods, which you can use to output the page to be served up. To see how this works, check out Listing 1 with the obligatory Hello World example.

Listing 1: Lua “Hello World”

01 function handle(r) 02 -- Set MIME type to text/plain: 03 r.content_type = "text/plain" 04 05 -- return the "Hello World" text: 06 r:puts("Hello World!") 07 end

Comments in Lua always start with two hyphens. r.content_type first changes the MIME type of the returned document to text/plain ; r.puts then outputs the Hello World string. To start the script, store it as hello.lua in the document root (if you followed the “Quick Install” example, this is ~/httpd/htdocs ). Now you can access the function in your browser by typing, for example:

http://localhost/hello.lua

If you have a typo, Apache returns an internal server error (number 500). You only get to see a Lua interpreter error message under special circumstances (Figure 1). If you forgot to set the MIME type, Apache will offer to download the Lua script by default.

Figure 1: mod_lua only reports run-time errors in the output.

Name Day

A script normally wants to evaluate data from a form. Listing 2 shows a simple example. The HTML file here creates a form that prompts the user for their first and last names and then uses a get to send them to the answer.lua script.

Listing 2: question.html with a Form

01 function handle(r) 02 -- Set MIME type to text/plain: 03 r.content_type = "text/plain" 04 05 -- return the "Hello World" text: 06 r:puts("Hello World!") 07 end

Listing 3 shows the content. The handler() function first sets the MIME type to text/html and then issues multiple r.puts() to create the start of the HTML document. The form then needs to be filled with data. Again, the request object will help here.

Listing 3: Evaluating the Form, answer.lua

01 require 'string' 02 03 function handle(r) 04 r.content_type = "text/html" 05 r:puts("<html>") 06 r:puts("<head><title>Answer</title></head>") 07 r:puts("<body>") 08 09 for k, v in pairs(r:parseargs()) do 10 if k=='firstname' then 11 firstname=v 12 elseif k=='lastname' then 13 lastname=v 14 end 15 end 16 17 r:puts(string.format("Hello %s %s!", firstname, lastname)) 18 19 r:puts("</body></html>") 20 end

The object’s r:parseargs() returns all the parameters sent by the Get method as a table. This data structure is known in other programming languages as an associative array or dictionary.

The table returned by parseargs() has for each field in the form an entry with the name of the field and the value stored in that field. To access the first and last names, the subsequent for loop iterates through the complete table. The pairs() function helps it do so by pointing to the next entry in the table. The form field name is stored in the variable k ; and v stores the matching content.

The if condition checks to see whether the name is a first name. If so, it is stored in the variable firstname . Similarly, the lastname variable stores the last names. Variables in Lua can be used directly to store arbitrary values.

Lua experts will probably object at this point, saying that you could save yourself the trouble of the loop and do the following

allparameters = r:parseargs(); firstname = allparameters["firstname"]

to fish the first names directly out of the table; however, this will provoke an error if the first name wasn’t transferred for some obscure reason.

After sending the first and last names off to their respective variables in Listing 3, the string.format() function concatenates to create a new string. The %s placeholder replaces string.format() with the content of the firstname and lastname variables. The results end up in r:puts() and, thus, in the finished HTML document (Figure 2). string.format() originates from Lua’s standard string library; the require command at the start includes this library.

Figure 2: The Answer script in the foreground accepts the query and creates a message from the names.

No POST Today

Listing 3 only works if the script receives the form content via Get. If the Post method is used, r:parsebody() will provide the desired data. You can identify the HTTP method used here with r.method . This means you can work with cases as shown in Listing 4.

Listing 4: Differentiating Post and Get

01 if r.method == 'GET' then 02 for k, v in pairs( r:parseargs() ) do 03 -- Evaluation for GET here ... 04 end 05 elseif r.method == 'POST' then 06 for k, v in pairs( r:parsebody() ) do 07 -- Evaluation for POST here ... 08 end 09 else 10 r:puts("Unknown HTTP method") 11 end

Although the official mod_lua documentation suggests using parsebody() , doing so in our lab only generated an error message (Figure 3).

Figure 3: Some mod_lua functions aren’t implemented.

A quick check of the mod_lua source code showed that the function isn’t actually implemented, or at least not in Apache 2.4.2. All other functions and attributes of the request object are listed in the “Data Structures” section of the official documentation for the module.

A Lua script needs to include a handle() function by default. The configuration directive LuaMapHandler can change this, however. If you add the line

LuaMapHandler /info /var/www/example.lua sayhello

to your httpd.conf file, for example, Apache should launch the sayhello function in the Lua script /var/www/example.lua when calling the http://localhost/info URL. The emphasis is on “should” here, because this directive is just not implemented (Figure 4) – like parsebody() – although test scripts for it actually exist in /modules/lua/test of the Apache source code. Other interesting directives are also missing, such as LuaCodeCache , which might at some time modify cache behavior for Lua scripts.

Figure 4: Some of the directives in the mod_lua documentation simply weren’t implemented.

Hooked

Hooks allow you to integrate DIY modules and thus effect changes in various web server processing phases. mod_lua gives you this ability for Lua scripts, too, which means you could implement a URL redirect as a Lua script. Listing 5 returns the forbidden.html file when a user attempts to access http://localhost/secret .

Listing 5: URL Redirect via Lua Script

01 require 'string' 02 require 'apache2' 03 04 function translate_name(r) 05 if r.uri == "/secret" then 06 r.uri = "/forbidden.html" 07 return apache2.DECLINED 08 end 09 return apache2.DECLINED 10 end

The apache2 library included in Listing 5 provides the constants shown in Table 1, among other things. Depending on the hook, other return values might make sense, such as an HTTP status code.

For Apache to know which Lua file needs to be called by which function and when, you need to add a line to your httpd.conf , as follows:

LuaHookTranslateName /usr/local/apache2/scripts/redirect.lua translate_name early

LuaHookTranslateName tells Apache to call the Lua translate_name function as early as possible when interpreting the request URL.

The function name in the Lua script could thus have a different name, but it makes sense to name it after the Apache hook, as in Listing 5. /usr/local/apache2/scripts/redirect.lua also supplies the full path to the script file. The optional early ensures that Apache runs the function as early as possible in the process flow. An alternative to this is late , which tells Apache to run the function as late as possible.

The official module documentation lists the other existing hook directives , but again, some of these aren’t implemented. For more sample scripts, in particular on the subject of hooks, check out the Apache source code directory below modules/lua/test .

Conclusions

Mod-lua is enticing: The Lua scripts launched by the module can put an HTML document together faster than their PHP or Perl counterparts, and you can even hook Lua scripts into the Apache server’s process chain. However, closer inspection of the module reveals many construction sites.

The documentation explicitly points out that the module’s configuration and approach could change at some time in the future. Seen in this light, you can understand that the official documentation is just a single, fairly terse, page, and this makes it all the more surprising that mod_lua has made the cut for the official Apache package. On the other hand, this will improve the module’s chances of being discovered by more developers – and this is something it definitely deserves.

Info

[1] Apache 2.4 download

[2] APR and APR-util