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.