Xavante
A Lua Web Server with CGILua support

Experimental Modules

These are a few experimental modules included with Xavante; they're fairly functional but might have very rough edges, or might not seem very useful at first.

In general, these modules can be used when writing new Xavante handlers. In my view (not shared by most Kepler developers), static pages and CGILua are only two particular ways to create websites and webapps. There are many other ways to achieve those goals, and when using Xavante, most of them start with writing a Handler.

A Xavante Handler is just a function that gets called to serve a client request. The Xavante core parses the request's URL, and decides which handler to call. The chosen handler receives as parameters the request and response objects, usually we call these "the (req,res) parameters".

The handler analyses these parameters and produces the desired response, either calling the res:send(data) function repeatedly, or setting the res.content field with a string or an array of strings.

cookies.lua

Cookies are a common feature of many web development platforms. This module is shamelessly copied from the CGILua implementation, and exports three functions to be used from handlers:

value = xavante.cookies.get (req, name)

xavante.cookies.set (res, name, value [, options])

xavante.cookies.delete (res, name [, options])

Where req and res are the request and response objects, respectively, the handler got from the Xavante core, name is the name under which the cookie will be stored in the browser, value is the value to store as a cookie, and options are the cookie's options, given as a name=>value table.

The cookie options are managed by the browser, but as a special case, if options.expires is a number, it's converted into the corresponding date string. This let's you use the os.time() function to specify a relative time.

session.lua

This module lets you store some data and make it persistant from one request to another by the same client. In the current implementation, the session's stored data is just a Lua table, and is only stored in memory. If Xavante is restarted, all session data is lost.

To recognize the client coming back, a cookie is stored in the client's browser with a randomly generated session ID (SID) as the value.

S = xavante.session.open (req, res [, name])

xavante.session.close (req, res [,name])

The name is optional but strongly suggested to use a string unique to your handler, to minimize any chance of collision. Remember to give the same name (if any) to the session.close() function.

The table S would be a new table the first time the client visits this webapp, but will be the same table each subsequent request. You can store any kind of Lua data in this table.

When the user wants to close his session, call the session.close() function. The cookie in his browser will be deleted, and the session table will be detached from the internal storage, so it will be eventually garbage collected.

codeWeb.lua

Xavante was developed to efficiently handle a big number of handlers, each one could manage as big or as little a part of the URL space as desired. There's nothing wrong with having several handlers to manage a single URL each. codeWeb is a module to help you with that kind of setting.

As usual with Lua scripts, the Xavante config file is a script that gets executed at startup time and sets up the running environment. In most cases, this means using the xavante.HTTP {} function to: associate the whole tree to xavante.filehandler, any file ending with .lp or .lua to xavante.cgiluahandler, and any URL ending with / xavante.redirecthandler.

The codeWeb module adds a function to register several handlers, joined as a module, to part of the URL tree:

xavante.codeWeb.addModule (host, urlpath, m [, as_tree])

Where host is the virtual host name, urlpath is the root of the URL subtree where the module gets registered, m is a table in the form m[name]=h, with name as the URL end part, and h as the corresponding handler. The optional boolean as_tree (false by default) tells if each handler should registered as a single URL, or as a subtree itself.

To use this function, the developer writes a module, where the exported functions are the handlers. The head of the subtree (urlpath itself) is managed by the handler called "__main". The function coroWeb.addModule() registers all table elements where the key is a string not beginning with '_' and the value is a function.

Example:

local session = require ("xavante.session")
require ("xavante.coroWeb")
module (arg and arg[1])

--
-- the 'main' handler redirects to .../index
--
function __main (req,res)
    local path = req.parsed_url.path
    if string.sub (path, -1) ~= "/" then path = path.."/" end
    xavante.httpd.redirect (res, path.."index")
end

--
-- this is a hard-coded constant page
--
function index (req, res)

    cookies.set (res, "mycook", "456", {["Max-Age"] = "60"})

    res.content = [[
        <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
        <HTML><HEAD>
            <TITLE>CodeWeb</TITLE>
        </HEAD><BODY>
            <H1>Hi there!</H1>

            look <a href="mira1">this</a>,
            and then <a href="mira2">that</a>.</br>
            <a href="subpage">here</a>'s another page.</br>

        </BODY></HTML>]]
    return res
end

--
-- this two pages use a common session to check user status
--
function mira1 (req, res)
    local ss = session.open (req, res, "mirando")

    res.content = [[
        <HTML><HEAD>
        <TITLE>CodeWeb</TITLE>
        </HEAD><BODY>
    ]]

    if ss.lastvio == "mira1" then
        res.content = res.content .. "<p> repeating.... </p>"
    elseif ss.lastvio == "mira2" then
        res.content = res.content .. "<p> should've come here first... </p>"
    else
        res.content = res.content .. "<p> haven't seen anythig yet! </p>"
    end

    res.content = res.content .. "</BODY></HTML>"

    ss.lastvio = "mira1"
end

function mira2 (req, res)
    local ss = session.open (req, res, "mirando")

    res.content = [[
        <HTML><HEAD>
            <TITLE>CodeWeb</TITLE>
        </HEAD><BODY>
    ]]

    if ss.lastvio == "mira1" then
        res.content = res.content .. "<p> see that? this one is better! </p>"
    elseif ss.lastvio == "mira2" then
        res.content = res.content .. "<p> repeating the second... </p>"
    else
        res.content = res.content .. "<p> see the other one first! </p>"
    end

    res.content = res.content .. "</BODY></HTML>"

    ss.lastvio = "mira2"
end

--
-- this page is loaded and compiled at startup time,
-- but executed at request time
--
subpage = xavante.codeWeb.load_cw ("test.cw");

This sample module defines five handlers: __main(), index(), mira1(), mira2() and subpage(). If this code is in the cw_samp.lua file, and you want it to be accessible under URLs of the form: http://your.server.com/sample, the config file should include a line like this:

xavante.codeWeb.addModule ("_", "/sample", require "cw_samp", true)

This line first does a require "cw_samp", which compiles the module and returns it as a table. Then the xavante.codeWeb.addModule() function registers it under the "/sample" subtree of the default virtual host (named "_"), making the handlers accessible under the URLs:

  • http://your.server.com/sample
  • http://your.server.com/sample/index
  • http://your.server.com/sample/mira1
  • http://your.server.com/sample/mira2
  • http://your.server.com/sample/subpage

The main() handler just redirects any request to index. In some projects this could be the most convenient (and most flexible) way to redirect or remap URLs.

The index() handler stores a cookie of name "mycook" and value "456" at the user's browser, with a maximum age of 60 seconds. Then it just responds with a constant HTML content.

The two handlers mira1() and mira2() use a session called "mirando", to store some data used to deduce the order in which the user clicked on those links and present some annoying messages.

The subpage handler uses the second feature of codeWeb: compiled templates.

A codeWeb template is a file written in the same way as luaPages for CGILua; in fact, the code is mostly copied from CGILua's luaPage translator. The main difference is that a template loaded by codeWeb.load_cw() gets compiled into a handler. This compilation usually happens at startup time, so the response time is as fast as other Xavante handlers.

This is the code of the test_cw template:

<html>
    <?lua
        local cookies = require ("xavante.cookies")

        mycook, mck_opt = cookies.get (req, "mycook")
    ?>
    <?lua if mycook then ?>
        <p>cookie: <?= mycook ?> </p>
        <ul><?lua for k,v in pairs (mck_opt) do ?>
            <li>opt: <?= k ?> = <?= v ?></li>
        <?lua end ?></ul>
    <?lua else ?>
        <p> there's no cookie </p>
    <?lua end ?>
</html>

Note: This is a bad example, there's almost no HTML code and lots of Lua, obscured by the many <?lua ... ?> tags.

As can be seen in the example, the req, and res objects are available. The handler built by the codeWeb.load_cw() is of the form

function (req, res, ...)
    -- translated template code
end

The Xavante core dispatcher is not the only one that can call handlers; any handler could call another handler. This is especially convenient to use several small templates to generate pieces of HTML code, under the controlling logic of an 'outer' handler.

To pass data between an 'outer' handler and a compiled template, just add more parameters after the req and res required parameters. If you wish, you could start your template with some code like:

<?lua
    local myparam1, myparam2 = unpack(arg)
?>

To give local names to your extra parameters.

coroWeb

This module uses sessions to keep track of a user, creating a coroutine to handle it as the code flow. Just write a simple handler, but instead of registering it, wrap it into a coroWeb handler, like this:

--
-- this is a coroHandler, the user progresses
-- through the code with each visit
--
coPage = xavante.coroWeb.handler ("coPage", function (req, res)
        res.content = "<html><body>one..</body></html>"
        req, res = yield ()
        res.content = "<html><body>two..</body></html>"
        req, res = yield ()
        res.content = "<html><body>and three, the end!</body></html>"
    end)

The coroWeb.handler() function takes as paramters a name (used for the session cookie), and a handler (a function with (req,res) parameters), and returns a new handler, which could (for example) be one of many in a codeWeb module.

A coroWeb handler produces some content (with the usual res parameter), and calls yield(). The function flow is suspended, the content is sent to the browser, and the function only gets resumed when the same user returns to the handler (on any URL managed by the same handler). At this point, the yield() function returns, with a new set of req,res parameters.

The handler gets started with each new user to enter the webapp; and the session is closed when the handler finishes.

In this example, the first time the user gets to this URL, he gets a page with "one..", the second time he gets "two..", and the third time he gets "and three, the end!". If he reloads the page a fourth time, the cycle starts again.

A developer could want to write a webapp with a central 'event loop', much like GUI apps are written. To help on this pattern, there's the xavante.coroWeb.event() function.

req,res = xavante.coroWeb.event (req, sh_t [, get_all])

This function should be called in the main loop; it returns a new set of req, res objects each iteration. The first parameter is the usual req object, sh_t is a table of subhandlers, and the optional get_all is a boolean (default false).

An event driven handler should first create the 'main' page on res and call coroWeb.event() with a table filled with the handlers for all possible actions of the user. Each of these subhandlers will be called with the req,res objects corresponding to the user action. If a subhandler returns a string "refresh", or if the main handler set the get_all parameter as true, the coroWeb.event() returns and the main loop should refresh the main page.

A simple example:

coPage2 = xavante.coroWeb.handler ("coPage2", function (req, res)

    local counter = 0
    local count2 = 0
    local sh_t = {
        inc = function () counter = counter+1 return "refresh" end,
        dec = function () counter = counter-1 return "refresh" end,
    }

    while true do
        res.content = string.format ([[
            <html><body>
                    counter: %s (%s)</br>
                <a href="coPage2/inc">inc</a>
                <a href="coPage2/dec">dec</a>
            </body></html>]], counter, count2)

        count2 = count2+1

        req,res = xavante.coroWeb.event (req, sh_t)
    end
end)

The first time a user enters this handler, two local variables, counter and count2 are initialized at 0, and the subhandler table sh_t is filled with two handlers: inc() and dec().

The main loop does three things: sets the response content with a simple page (which includes the counters values and links to the inc and dec subhandlers), increments the count2 variable, and waits for an event.

When the user clicks on a link, the corresponding subhandler is executed and the counter variable is modified. The subhandler then returns a "refresh" string, which causes the event() function to return immediately to the main loop and refresh the main page, with the updated counter variables.

Note that these handlers don't use req and res, so we can leave the parameter lists empty. This is possible because all the data used by the subhandlers is accessible as local variables (local to the enclosing scope), and all the output needed is just refreshing the main page (in the main loop). What's missing from this example is a "logout" link that would let the main loop to finish, and eventually close the thread.

Valid XHTML 1.0!