WSAPI et Xavante : serveur Web Lua

Introduction

Image23

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

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

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

Cette entrée a été publiée dans Domotique, Tutoriels, Eloise and taguée . Placez un signet sur le permalien.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *