Introduction
Contexte
L’objet de ce tutoriel est de détailler l’installation et le fonctionnement d’un serveur web permettant d’héberger un programme Lua : WSAPI-Xavante. La documentation concernant ce serveur web n’est pas très prolixe, pas très pédagogique et pas très francophone, d’où tout l’intérêt de ce tutoriel.
Dans mon cas d’utilisation, je compte héberger « l’intelligence » de ma domotique. C’est parce que celle-ci est développée en Lua que j’ai choisi un serveur web Lua : Xavante basé sur l’API WSAPI
WSAPI
WSAPI est une API permettant aux applications Web Lua de s’abstraire du serveur Web. Ainsi, une application codée en utilisant l’API WSAPI peut fonctionner sur n’importe quel serveur supportant cette API (actuellement CGI, FastCGI et Xavante).
WSAPI fournit un ensemble de librairies destinées à faciliter de traitement des requêtes ainsi que la bufferisation des sorties.
Xavante
Xavante est un serveur Web HTTP 1.1 Lua qui utilise une architecture modulaire basée sur des gestionnaires (handlers) de correspondance d’URI (URI mapped handlers). Xavante implémente des gestionnaires de fichier, des gestionnaires de redirection et des gestionnaires WSAPI. Ces gestionnaires sont respectivement utilisés pour des fichiers, des réécritures d’URI et des fonctions WSAPI. La correspondance d’URI se fait via l’écriture d’expressions régulières au sens Lua du terme.
Mise en œuvre
Installation
Si ce n’est déjà fait, la première étape est d’installer Lua ainsi que le système de gestion et de déploiement de modules Lua LuaRocks :
$ sudo apt-get install lua $ sudo apt-get install luarocks |
Il est ensuite temps d’installer Xavante et WSAPI :
sudo luarocks install xavante sudo luarocks install wsapi-xavante |
Exemple
Voici un exemple de mise en œuvre (le fichier se nomme tuto01.lua
) :
---------------------------------------------------------------- -- Exemple de mise en oeuvre de WSAPI et Xavante (tuto01.lua) -- ---------------------------------------------------------------- local xavante = require("xavante") local filehandler = require("xavante.filehandler") local wsx = require("wsapi.xavante") local redirecthandler = require("xavante.redirecthandler") -- Chemin des documents html et des scripts lua local webDir = "/tmp/www" function fonctionSimple(req, res) res.headers["Content-type"] = "text/html" res.content = string.format("Fonction toute simple (%s)<br><br>req.serversoftware = %s", tostring(os.date()), tostring(req["serversoftware"])) return res end function fonctionWSAPI(wsapi_env) local headers = { ["Content-type"] = "text/html" } local function hello_text() coroutine.yield("<html><body>") coroutine.yield("<p>Ma fonction WSAPI !</p>") coroutine.yield("<p>PATH_INFO: " .. wsapi_env.PATH_INFO .. "</p>") coroutine.yield("</body></html>") end return 200, headers, coroutine.wrap(hello_text) end local mesRegles = { { -- Gestionnaire de fichiers html (http://localhost:8080/index.html) match = "%.html", with = filehandler, params = {baseDir = webDir} }, { -- Gestionnaire de fonction (http://localhost:8080/fsimple) match = "/fsimple$", with = fonctionSimple }, { -- Gestionnaire de redirection (http://localhost:8080/redirection) match = "/redirection$", with = redirecthandler, params = {"/fsimple"} }, { -- Gestionnaire de fonction WSAPI (http://localhost:8080/fwsapi) match = "/fwsapi$", with = wsx.makeHandler(fonctionWSAPI) }, { -- Gestionnaire de scripts WSAPI (http://localhost:8080/hello.lua) match = { "%.lua$", "%.lua/" }, with = wsx.makeGenericHandler(webDir) }, } -- Message de départ xavante.start_message( function(ports) local date = os.date("[%Y-%m-%d %H:%M:%S]") print(string.format("%s Xavante started on port(s) %s", date, table.concat(ports, ", "))) end ) -- Configuration xavante.HTTP{ server = { host = "*", port = 8080 }, defaultHost = { rules = mesRegles }, } -- Fonction callback function callback() print(os.date()) return false end -- Démarrage (Dans un terminal : wsapi -c tuto01.lua -p 8080) xavante.start(callback,1) |
Pour lancer le programme, il faut saisir dans un terminal : wsapi -c tuto01.lua -p 8080
Configuration
Après que le module Xavante ait été chargé, il faut le configurer avec la fonction xavante.HTTP
pour qu’il puisse répondre à des requêtes. Il faut définir le port sur lequel le serveur écoute puis enregistrer tous les gestionnaires (handlers) :
xavante.HTTP{ server = { host = "*", port = 8080 }, defaultHost = { rules = simplerules }, } |
Xavante peut servir plusieurs sites, dans ce cas, il faut enregistrer les gestionnaires individuellement pour chacun des sites :
xavante.HTTP{ server = { host = "*", port = 8080 }, defaultHost = {}, virtualhosts = { ["www.sitename1.com"] = simplerules1, ["www.sitename2.com"] = simplerules2 } } |
Démarrage et fonction callback()
Pour démarrer Xavante, il faut appeler la fonction xavante.start()
. Cette fonction peut prendre 2 paramètres optionnels : une fonction callback
et un délai en secondes. Quand Xavante a la main (ie. est en attente d’une requête), la fonction callback
est appelée tous les x secondes (1 dans l’exemple de code). Xavante s’arrête de fonctionner si la fonction callback
retourne true. Cette fonction permet d’implémenter des comportements qui ne correspondent pas à des réactions à des requêtes.
Dans le cadre d’une application de domotique, c’est l’endroit idéal pour gérer les timers (cron). Personnellement, j’ai choisi comme délai 0.05
. C’est la plus petite valeur qui ne surcharge pas mon Raspberry. Ainsi le système est un peu bicéphale. Une partie des traitements sont déclenchés par des requêtes (ex : changement d’état d’un module) et l’autre partie par la fonction callback
(ex : timers). Pour que le système reste bien réactif, il faut que la durée de ces traitements soient la plus courte possible (moins d’une seconde). Il faut donc, entre autres, bien gérer les timeout des appels systèmes et des requêtes initiées par l’application.
Les gestionnaires (Handlers)
L’ordre dans lequel les gestionnaires sont enregistrés est important car c’est dans cet ordre que les tentatives de correspondance d’url vont se faire.
Gestionnaire de fichier
{ -- Gestionnaire de fichiers html (http://localhost:8080/index.html) match = "%.html", with = filehandler, params = {baseDir = webDir} }, |
C’est le gestionnaire le plus simple qui permet d’afficher des fichiers html situés dans /tmp/www
.
Supposons que dans /tmp/www
se trouve le fichier html index.html
suivant :
<html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>Mon site sous WSAPI Xavante</title> </head> <body> <em>index.html</em> du site. </body> </html> |
Dans ce cas, une fois le serveur web lancé, en saisissant dans un navigateur web l’adresse http://localhost:8080/index.html
on doit obtenir l’affichage du fichier index.html
qui affiche simplement index.html du site
.
Gestionnaire de fonction
{ -- Gestionnaire de fonction (http://localhost:8080/fsimple) match = "/fsimple$", with = fonctionSimple }, |
Ce gestionnaire permet de faire correspondre une url au résultat d’une simples fonction Lua. Cette fonction doit comporter deux paramètres : req
et res
. req
est une table contenant des informations sur la requête ayant invoqué la fonction et res
est une table qui permet de spécifier le résultat qui sera affiché dans le navigateur.
La manière de manipuler les données d’une requête GET ou POST avec ce type de fonction est détaillée dans un autre billet (Xavante : Handler de type fonction Lua simple).
Dans cet exemple, en saisissant dans un navigateur web l’adresse http://localhost:8080/fsimple
on doit obtenir un affichage semblable à :
Fonction toute simple (Tue Mar 10 17:33:31 2015) req.serversoftware = Xavante 2.2.0
Gestionnaire de redirection
{ -- Gestionnaire de redirection (http://localhost:8080/redirection) match = "/redirection$", with = redirecthandler, params = {"/fsimple"} }, |
Le gestionnaire de redirection permet de faire un alias d’url.
Dans l’exemple de ce tutoriel, l’url http://localhost:8080/redirection
doit produire exactement le même résultat que l’url http://localhost:8080/fsimple
.
Gestionnaire de fonction WSAPI
{ -- Gestionnaire de fonction WSAPI (http://localhost:8080/fwsapi) match = "/fwsapi$", with = wsx.makeHandler(fonctionWSAPI) }, |
Le gestionnaire de fonction WSAPI permet de faire correspondre une url à une application WSAPI. Une application WSAPI est une fonction Lua qui reçoit comme paramètre un environnement WSAPI et qui retourne un code de statut (status code), une en-tête de réponse (response headers) et un itérateur permettant de générer la page résultat (output iterator) :
function fonctionWSAPI(wsapi_env) local headers = { ["Content-type"] = "text/html" } local function hello_text() coroutine.yield("<html><body>") coroutine.yield("<p>Ma fonction WSAPI !</p>") coroutine.yield("<p>PATH_INFO: " .. wsapi_env.PATH_INFO .. "</p>") coroutine.yield("</body></html>") end return 200, headers, coroutine.wrap(hello_text) end |
Dans l’exemple de ce tutoriel, l’url http://localhost:8080/fwsapi
doit générer le résultat suivant :
Ma fonction WSAPI ! PATH_INFO: /fwsapi
Gestionnaire de scripts WSAPI
{ -- Gestionnaire de scripts WSAPI (http://localhost:8080/hello.lua) match = { "%.lua$", "%.lua/" }, with = wsx.makeGenericHandler(webDir) }, |
Les applications WSAPI ne sont généralement pas implémentées comme de simples fonctions mais sont packagées dans un module Lua comportant une fonction run servant de point d’entrée. Voici un exemple de fonction packagée de cette manière dans le fichier hello.lua
à placer dans le répertoire /tmp/www
:
#!/usr/bin/env wsapi.cgi local _M = {} function _M.run(wsapi_env) local headers = { ["Content-type"] = "text/html" } local function hello_text() coroutine.yield("<html><body>") coroutine.yield("<p>Hello Wsapi!</p>") coroutine.yield("<p>PATH_INFO: " .. wsapi_env.PATH_INFO .. "</p>") coroutine.yield("<p>SCRIPT_NAME: " .. wsapi_env.SCRIPT_NAME .. "</p>") coroutine.yield("</body></html>") end return 200, headers, coroutine.wrap(hello_text) end return _M |
La ligne #!/usr/bin/env wsapi.cgi
indique aux serveurs web tournant sous Linux comme Apache qu’il faut exécuter le lanceur CGI générique de WSAPI quand le script est exécuté comme un script CGI.
Dans l’exemple de ce tutoriel, l’url http://localhost:8080/hello.lua
doit générer le résultat suivant :
Hello Wsapi! PATH_INFO: / SCRIPT_NAME: /hello.lua
Références
GitHub : keplerproject/xavante
GitHub : keplerproject/wsapi
Lua 5.2 Reference Manual : 6.4.1 – Patterns
Billet sur ce blog : Xavante : Handler de type fonction Lua simple
Billet sur ce blog : Xavante : Handler de type fonction WSAPI
Sommaire Domotique sur ce blog