Apache 2.4 with mod_lua
Moonrise
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 [1]. 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 [2], 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 [3], 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 [4].
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 [7] with the obligatory Hello World example. 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:
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
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.
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 <html> 02 <head><title>Question</title></head> 03 <body> 04 <p>Please enter your first and last names:</p> 05 <form action="answer.lua" method="get"> 06 <input type="text" name="firstname"> 07 <input type="text" name="lastname"><br> 08 <input type="submit" value="Send!" /> 09 </form> 10 </body> 11 </html>
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.
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. Although the official mod_lua
documentation suggests using parsebody()
, doing so in our lab only generated an error message (Figure 3). 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 [8].
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
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.
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.
Tabelle 1: Standard Return Values for Hooks
Value |
Meaning |
---|---|
|
Request successful |
|
Request not processed |
|
Request completely processed |
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 [8], 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 [8], 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.