[couverture de Linux Magazine 92]

Mason - Première partie

Article publié dans Linux Magazine 92, mars 2007.

Copyright © 2007 - Damien Krotkine.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau

Mason est un framework, basé sur mod_perl, qui permet de créer des sites web dynamiques. Puissant et polyvalent, son succès est grandissant. Utilisable aussi bien de manière autonome qu'encapsulé dans un autre framework, son terrain de jeu est très vaste.

Cet article présente la mise en œuvre de Mason dans un environnement classique Apache/mod_perl. Il sera suivi d'un second article traitant les concepts avancés, et enfin d'une étude de cas détaillée, avec code source complet.

HTML::Mason : Introduction

Perl Mason est un framework complet qui permet de « créer, servir et gérer des sites web ». Créé en 1998 et développé principalement par Jonathan Swartz et Dave Rolsky, Mason est plutôt facile à apprendre et polyvalent. Il peut se comparer à PHP pour sa facilité d'utilisation, à JSP pour son design moderne. Et surtout, Mason permet d'utiliser toute la puissance de Perl depuis un environnement totalement orienté web.

Une définition simple de Mason peut être : « un ensemble de modules pour dynamiser du contenu statique ». Cette description est très vague, car Mason peut générer n'importe quel type de contenu, même si on l'utilise principalement pour du HTML. Mason peut être appelé depuis un programme Perl autonome, ou bien à partir d'un CGI, voire être appelé depuis un framework de complexité plus grande, pour répondre aux besoins de mises en forme. Ainsi, Mason est une option parmi les différents modules de template de Catalyst, un MVC en Perl. Jifty [1] est un autre exemple de framework web prometteur basé sur Mason.

MVC (Modèle Vue Contrôleur)

C'est une méthode de conception d'applications, plutôt ancienne (1979) qui a été mise au goût du jour par le succès de frameworks web développés suivant ce principe. Cette architecture instaure une séparation entre les données (Modèles), la présentation (Vues), et le traitement des événements (Contrôleurs).

Mason est utilisé principalement dans le cadre de programmation web orientée page. Il est généralement mis en œuvre par dessus le couple Apache/mod_perl. Ses atouts sont une syntaxe puissante, un jeu de modules proposant beaucoup de fonctionnalités, et la possibilité d'utiliser facilement des modules Perl tiers. Il peut également représenter une page en utilisant le modèle objet. Mason peut être vu comme le PHP de Perl, mais ne se limite pas à cela.

Hello World !

Commençons par l'inévitable hello world. Voici une page HTML enrichie grâce à la syntaxe de Mason :

  <html>
  <body>

  % my $variable = 'World';
  Hello <% $variable %>!
  <%perl>
  use POSIX qw(uname);
  my $systeme = (uname())[0];
  </%perl>
  Ce serveur web utilise <% $systeme %>.

  </body>
  </html>

Après un examen rapide de ces quelques lignes, on peut en déduire les règles de base :

Cet exemple n'est qu'un petit extrait de ce qu'il est possible de faire avec Mason, mais illustre la philosophie globale du framework : être accessible et facile d'utilisation, tout en restant concis. Ceci est possible grâce à l'utilisation de Perl directement dans la page web, contrairement à d'autres syntaxes, par exemple Template::Toolkit, qui nécessitent l'apprentissage d'un nouveau langage.

Exemples de syntaxe

Voici quelques structures de code qu'on rencontre couramment dans une page Mason :

Commentaires

Voici les différents types de commentaires :

  %# ceci est un commentaire
  <% # Ceci est également un commentaire, rarement utilisé %>

Attention, la première syntaxe requiert que le signe % soit le premier caractère de la ligne.

Condition

Afficher du contenu HTML de manière conditionnelle :

  % if ($produit->get_stock() <= 0) {
    <span class='alert'>Produit indisponible</span>
  % }

Cela permet de mélanger perl et HTML. Dans ce cas, La page affiche « Produit indisponible » uniquement si le produit n'est plus en stock.

Boucle

Au même titre qu'un test conditionnel, il est facile d'utiliser for, foreach, while et autres <unless :>

  <ul>
  % foreach my $attribut ($produit->get_attributs) {
  %   my $cle = $attribut->{cle}
  %   my $valeur = $attribut->{valeur}

      <li> <% $cle %> : <% $valeur %> </li>

  % }
  </ul>

Appel à un module Perl

Appeler un module Perl dans une page web est très simple, voici un exemple de code qui vérifie si le drapeau utf8 est vrai pour une variable :

  <%perl>
    use Encode;
    my $chaine = "Exemple de chaîne de caractère avec accents";
    my $drapeau_utf8 = Encode::is_utf8();
  </%perl>

  % if ($drapeau_utf8) {
    <span class='info'>La représentation interne de la chaîne "<% $chaine %>" est en UTF8</span>
  % } else {
    <span class='info'>La représentation interne de la chaîne "<% $chaine %>" est en latin1</span>
  % }

Un autre exemple typique est d'utiliser Data::Dumper pour afficher le contenu d'une variable. Dans le code suivant, nous appelons la méthode Dumper() sur une variable $a, mais au passage nous remplaçons les retours à la ligne par des retours chariots HTML.

  % use Data::Dumper;
  % my $a = { foo => 'bar' }
  <% join("<br>\n", split("\n", Dumper(\$a))) %>

Les différents intervenants

Un site Web basé sur Mason repose en général sur trois types de fichiers :

Séparer les rôles de chaque page par type permet un développement plus structuré. Bien sûr ce n'est qu'une convention, il faut donc avoir la volonté de la suivre. Cependant cela fait partie des Bonnes Pratiques du développeur Mason.

Configuration d'Apache

Mise en œuvre express

Mason est un module Perl, dont le nom exact est HTML::Mason [2]. Il peut être utilisé en mode autonome, en CGI, ou via mod_perl. Nous nous concentrerons sur l'utilisation de Mason avec le couple Apache/mod_perl.

Pour commencer, il nous faut un Apache avec mod_perl. Les exemples présentés ici utilisent Apache 1.x et mod_perl 1.x. Mason est compatible mod_perl 2 à partir de la version 1.29_02. Bien sûr, cela ne change rien à son utilisation. La plupart des distributions Linux permettent d'installer facilement le couple Apache/mod_perl. Une majorité d'entre elles proposent des paquetages Mason récents, mais si ce n'est pas le cas, il peut être installé à partir de CPAN.

Voyons rapidement la configuration du serveur Web. Ces lignes sont à rajouter dans le fichier de configuration d'Apache, par exemple /etc/apache/httpd.conf :

  PerlModule HTML::Mason::ApacheHandler
  
  <Location />
    SetHandler   perl-script
    PerlHandler  HTML::Mason::ApacheHandler
    PerlSetVar   MasonDataDir /var/cache/apache
  </Location>

Ceci permet de faire passer toutes les pages du site Web par l'interpréteur Mason. Celui-ci va analyser le contenu, exécuter les commandes spéciales et produire une page résultat.

On notera également la ligne

  PerlSetVar MasonDataDir /var/cache/apache

« Code Cache »

Pour des raisons de performances, Mason précompile les pages à servir. Les objets précompilés sont stockés sur le disque dur, dans ce qu'on appelle le « Code Cache ». Cela n'a rien à voir avec le système de cache utilisable par le développeur, que nous verrons dans le prochain article.

Elle permet d'indiquer à Mason où stocker le contenu du « Code Cache ». Nous la spécifions ici car c'est généralement la première chose qui bloque l'installation rapide de Mason, et la création d'une première page. En effet, ce répertoire doit être accessible en écriture par l'utilisateur système à qui appartient le processus Apache.

Ainsi configuré, Apache doit pouvoir servir des pages web Mason. Généralement, le serveur web affiche une page par défaut pour signifier qu'il fonctionne bien, située par exemple dans /var/www/localhost/htdocs/index.html. Nous pouvons tester si Mason fonctionne bien en introduisant une de ses balises dans ce fichier. <% 6 * 7 %> fera l'affaire. Si 42 apparaît lorsque la page est chargée, alors cela fonctionne correctement.

Configuration avancée

La configuration d'Apache que nous avons utilisée est un peu simpliste, voire franchement mauvaise, car tous les types de contenu seront interprétés. Cela pose un problème, car les images et autres fichiers binaires seront analysés. La performance s'en trouvera dégradée, et pour peu que leur interprétation en ASCII contienne des balises Mason, le site web sera mal rendu. Filtrons donc l'application de l'analyseur syntaxique sur les suffixes qui nous intéressent :

  PerlModule HTML::Mason::ApacheHandler

  <LocationMatch "\.html$">
    SetHandler perl-script
    PerlSetVar MasonDataDir /var/cache/apache
    PerlHandler HTML::Mason::ApacheHandler
  </LocationMatch>
  
  <LocationMatch "(\.mhtml|dhandler|autohandler)$">
    SetHandler perl-script
    PerlInitHandler Apache::Constants::NOT_FOUND
  </LocationMatch>

Nous indiquons à Apache qu'il doit servir les pages au suffixe .html via Mason, et renvoyer un 404 pour les .mhtml, mais également pour les dhandler et autohandler (voir plus bas), car nous ne voulons pas qu'un internaute puisse voir leur contenu.

Quand faut-il redémarrer Apache ? Mason est intelligent, il précompile les pages à servir, et se rend compte lorsqu'elles ont été modifiées. Ainsi il n'est pas nécessaire de redémarrer Apache lorsque l'on modifie une page HTML + Mason ou Mason pure. Par contre, si votre site web utilise des modules Perl, il faudra redémarrer Apache à chaque changement dans ces modules (ou utiliser Apache::Reload).

Paramètres de configurations

Paramétrer Mason peut se faire depuis le fichier de configuration d'Apache, ou bien depuis du code Perl. Une règle de nommage est à connaître : en Perl, la notation est l'utilisation de minuscules et d'espaces soulignés (underscore, _). Quant au paramètre Apache, il s'écrit avec le préfixe Mason et avec des majuscules au début de chaque mot. Ainsi, on aura parametre_en_perl et MasonParametreApache.

Voici un exemple d'utilisation dans httpd.conf :

  PerlSetVar MasonSessionClass Apache::Session::File

Cette ligne spécifie la classe à utiliser pour stocker les sessions.

Quelques paramètres souvent utilisés :

Tester la configuration

Une fois configuré, il ne reste plus qu'à démarrer le serveur Apache, et placer un fichier de test à la racine du site web. Par exemple, text.html qui contient :

  <html><body>
  Test : <% 40 + 2 %>
  </body></html>

En ouvrant un navigateur web sur cette page, le nombre 42 devrait apparaître.

Les composants Mason

Description

Un composant est un morceau de site web. Un site web est généralement constitué de plusieurs pages. Ces pages sont construites à partir de plusieurs éléments : par exemple un menu, un bandeau supérieur, un bandeau inférieur, un cadre de contenu, un encart de navigation, etc. Les composants Mason permettent d'implémenter ces éléments, et de les assembler. Prenons en exemple un petit site personnel. Considérons que chaque page est structurée comme suit :

Ce site web contient plusieurs pages :

Nous pouvons structurer le site web comme suit : un composant pour chaque page, lui même constitué de composants pour les bandeaux supérieur et inférieur, la navigation et le contenu.

Syntaxe

Un composant est un fichier. Il n'a pas besoin de déclaration. Un fichier test.html contenant du HTML et du Mason est un composant sans le savoir.

Lors d'une requête sur le site web, Mason doit décider quel composant (appelé « top-level ») va être interprété en premier. Il est choisi en fonction de l'url. Par exemple, de l'adresse http://www.siteweb.com/produits/cuillere.html?modele=7 Mason déduit qu'il lui faut appeler le composant cuillere.html, dans le répertoire produits/. S'il n'existe pas (si le fichier /produits/cuillere.html est inconnu), alors Apache renverra une erreur 404. En fait, nous verrons plus tard qu'il est possible d'intercepter ce 404.

Appeler un composant

Nous venons de le voir, un composant peut s'appeler simplement en entrant son URL. Mais les composants peuvent bien sûr s'appeler entre eux. On utilise cette syntaxe :

  <& chemin/du/composant.html, arguments_optionels &>

Le composant appelé sera interprété, et le contenu HTML résultant inséré en lieu et place de l'appel. Le chemin du composant suit la syntaxe des URI, donc le séparateur est /, et il peut être absolu ou relatif. Le répertoire courant est celui du composant appelant.

Voici le composant de la page d'accueil. Nous allons le créer dans index.html, ça sera la page par défaut de notre site web d'exemple :

  %# index.html
  %# Début du composant index
  
  %# on appelle le bandeau supérieur
    <& bandeau_sup.html &>

  %# on appelle le menu de navigation
  %# on passe le nom de la page actuelle
    <& navigation.html, page_courante => 'index' &>

  %# Voici le contenu spécifique de la page d'accueil

  <center>Bonjour et bienvenue sur ce site web !</center>

  %# le bandeau inférieur
  <& bandeau_inf.html &>

Créons maintenant le composant du bandeau supérieur qui s'appelle bandeau_sup, qui va contenir les balises de début de page, et un logo. Editons donc le fichier bandeau_sup.html :

  %# bandeau_sup.html
  <html>
  <head></head>
  <body>
  <center><img src="/images/logo.png"></center>

C'est plutôt simple. Bien sûr, ce HTML n'est qu'un exemple, il n'est pas du tout conforme aux normes W3C. Voyons maintenant le composant de navigation. Le menu de navigation doit présenter un lien vers les autres pages du site web, mais pas vers celle affichée actuellement, car c'est redondant. Ainsi, sur la page d'accueil, le menu de navigation permet de se rendre sur la « page de brèves », ou vers l'« à-propos ». Sur la page « à-propos », le menu de navigation propose les liens vers la « page d'accueil » ou la « page de brèves». C'est pourquoi nous passons en argument le nom du lien à ne pas afficher (voir plus bas en détail le passage d'arguments). Voici le composant navigation :

  %# navigation.html
  
  %# récupération de l'argument
  <%args>
  $page_courante
  </%args>

  %# on commence une table
  <table>

  %# on boucle sur tous les liens
  % foreach(qw(index breves apropos)) {
  %# si le lien n'est pas la $page_courante, on l'affiche
  %   if ($_ ne $page_courante) {
  %# on affiche le HTML du lien, construit à la volée, dans une ligne de la table
        <tr><td> <a href="<% $_ . 'html' %>">$_</a> </td></tr>
  %   } 
  % }

  %# on ferme la table
   </table>

Et pour finir voici le source du bandeau inférieur qui ferme la page web :

  %# bandeau_inf.html
  </body>
  </html>

Le fichier index.html est chargé par défaut lorsque l'utilisateur navigue sur le site web. Il est cependant également possible d'appeler les autres pages (donc composants) du site. Ainsi l'URL http://www.siteweb.com/bandeau_sup.html permet d'appeler le composant bandeau_sup.html.

Passer des arguments

Voici quelques précisions sur le passage d'arguments d'un composant à un autre. Les arguments peuvent être de tout type : scalaire (nombre ou chaîne de caractères), tableau, ou hash. Ils peuvent être obligatoires ou optionnels. Voici un petit code d'exemple de passage d'arguments. Le prototypage est faite dans le composant appelé :

  <%args>
  $a
  @b       # des arguments obligatoires, de type scalaire, tableau ou hash
  %c
  $d => 5  # voici un argument scalaire optionnel, et sa valeur par défaut
  $e => $d * 2
  @f => ('foo', 'bar')
  %g => (joe => 1, bob => 2)
  $h => undef # optionnel, par défaut undef
  </%args>

Et @_ ?

Si vous n'aimez pas le passage de paramètres nommés, Il est toujours possible d'utiliser @_ :

  # côté appelant
  <& composant, 42, 'chaîne' &>

  # côté appelé
  my ($reponse, $chaine) = @_;

Le passage des arguments se fait par nom. $a, @b et %c sont des arguments obligatoires, de type scalaire, tableau et hash. $d est un argument scalaire optionnel, car il a une valeur par défaut définie, 5. $e est également optionnel, mais sa valeur par défaut est dépendante de $d. @f et %g sont des arguments optionnels, avec des valeurs par défaut. La définition des arguments est à mettre entre les balises <%args> et </%args>. Les variables ainsi définies peuvent être utilisées dans le reste du composant, et seront locales au composant. La règle a retenir est : un argument avec une valeur par défaut est optionnel, sinon il est obligatoire.

Du côté du composant appelant, voici comment passer des arguments :

  % <& composant, a => 5 &>
  % <& composant, b => ['une', 'référence', 'de', 'liste'] &>
  % <& composant, c => { un => 'hash', en => 'référence'} &>

Il est possible de faire appel à des fonctions avancées de passage d'arguments, comme utiliser @_ ou %ARGS directement, mais nous ne les aborderons pas ici, car elles sont plus rarement utilisées.

Nous avons vu précédemment qu'il est possible d'accéder à un composant directement par son URL. Bien sûr, il est possible de spécifier des arguments dans l'URL. Ainsi, pour appeler le composant navigation.html en lui passant un nom de page, nous utiliserons cette syntaxe :

  http://www.siteweb.com/navigation?page_courante=index

Voici comment passer en argument d'URL le tableau @b et le hash %c, vu ci-dessus :

  http://www.siteweb.com/composant?b=une&b=reference&b=de&b=liste
  http://www.siteweb.com/composant?c=un&c=hash&c=en&b=reference

Params::Validate

Ce style de passage d'argument est disponible en dehors de Mason grâce au module Params::Validate de Dave Rolsky (un des auteurs de Mason). Ce module permet de passer des arguments comme le fait Mason, et de les vérifier.

Le passage d'arguments dans Mason est simple et puissant, et couvre tout type de donnée. Comme dans la quasi-totalité des frameworks web modernes, il est également transparent, c'est à dire que le développeur web n'a pas à se soucier de la provenance des arguments, ni de leur récupération manuelle. Ainsi, comme il est facile de passer des arguments d'un composant à un autre, il est possible (et recommandé) de faire beaucoup de composants distincts, un par fonctionnalité.

Les sections spéciales

Définition

Il existe quelques balises spéciales en Mason, qui permettent de déclarer des sections à l'intérieur d'un composant. Les sections sont des blocs qui contiennent du Perl uniquement, qui sont donc exécutées dans un contexte particulier.

<%init>

Commençons par une section simple : <%init>. Le contenu de ce bloc sera interprété au début du composant, même si la section <%init> est définie à la fin du composant. C'est utile pour séparer l'initialisation de variables Perl, qu'on peut reléguer à la fin du composant, et le contenu HTML de la page, qu'il est préférable de présenter au début du composant. Voici un exemple de syntaxe de ce bloc :

  %# début du composant

  <html>
    <head>
      <title>titre</title>
    </head>
  <body>
  %# Contenu de la page, qui utilise des variables
  Bonjour, nous sommes le <% $day %> du mois.
  </body>
  </html>

  %# Section init, utilisée pour initialiser $day
  <%init>
    $day = (localtime(time))[3];
  </%init>

Dans cet exemple, la section <%init> évite d'avoir à écrire la ligne d'initialisation de $day au tout début du composant. On peut placer <%init> n'importe où dans le composant, on est sûr que son code sera interprété au début du composant. Les lecteurs avertis se demanderont s'il est possible d'avoir plusieurs sections <%init> dans un seul composant. La réponse est : oui, c'est possible, même si ce n'est pas conseillé. Dans le cas de plusieurs sections <%init>, le code sera interprété dans l'ordre des sections <%init>.

Il est utile de noter que <%init> est interprété à chaque appel du composant, contrairement à d'autres sections.

<%cleanup>

<%cleanup> est l'inverse de <%init>, son contenu est exécuté à la fin du composant. <%cleanup> est rarement utilisé, car les variables normales du composant sont détruites à la fin de celui-ci (on dit qu'elles sortent de la portée du composant). <%cleanup> est donc utile uniquement dans le cas où un traitement spécial est à effectuer à la fin, par exemple libérer une connexion à une base de données.

<%once>

<%once> est une section dont le contenu est exécuté une seule fois, à l'initialisation du composant. Apache crée plusieurs processus (ou des threads, selon sa configuration), pour répondre aux requêtes des utilisateurs du site web. Un composant est initialisé la première fois qu'il est demandé par un utilisateur, pour un processus donné. Une fois initialisé, le composant est précompilé, et mis en cache interne de Mason. <%once> permet d'exécuter du code Perl lors de cette initialisation. <%once> ne sera plus jamais appelé avant qu'un nouveau processus soit créé par Apache, ou que Apache soit redémarré.

Quelle est donc l'utilité de <%once> ? Cette section est très pratique pour déclarer des variables, qui seront valides pendant toute la durée de vie du processus, et qu'on pourra initialiser une seule fois. Par exemple, il est pratique d'utiliser conjointement <%init> et <%once> pour créer une connexion à une base de données permanente :

  <%once>
    # déclaration de l'objet database handler
    use DBI;
    my $db;
  </%once>
  
  %# Au début de l'appel du composant, on initialise $db si besoin

  <%init>
    if (!defined $db) {
      $db = DBI->connect('dbi:mysql:dbname=foobar');
    }
  </%init>

  %# $db est utilisable dans le composant

<%filter>

Dans une section <%filter>, la variable spéciale $_ contient le HTML final de la page, tel qu'il va être renvoyé. <%filter> permet donc de modifier le résultat final d'un composant : il suffit d'altérer $_. L'exemple suivant passe le HTML de la page en majuscule.

    <%filter>
    y/a-z/A-Z/;
    </%filter>

La section <%filter> est intéressante pour opérer une transformation sur le flux en sortie, sans se préoccuper de la manière dont il a été créé. Cependant cela peut nuire à la lisibilité et à la maintenabilité du code, car le fait de multiplier les interventions sur les données, dans des endroits différents du code, peut complexifier les processus.

Autres sections

Exemple d'utilisation des sections

Voici un exemple concret d'utilisation des sections. Pour un développement web conséquent, il peut être important de soigner la forme du code HTML renvoyé. Notamment, un code HTML source bien indenté est utile pour repérer et comprendre les problèmes d'affichage d'une page web. Mais indenter du HTML dans un composant Mason n'est généralement pas possible : certaines portions d'HTML sont incorporées dans certains cas uniquement, on ne peut pas prévoir l'indentation avant que les pages ne soient générées totalement.

La solution est d'indenter le HTML à la volée, avant de le renvoyer vers le navigateur de l'internaute. Ce faisant, nous pouvons également en profiter pour analyser syntaxiquement le HTML grâce à un module ad-hoc ; si cet analyseur détecte des erreurs, les lignes incriminées peuvent être affichées en bas de page, en couleur.

Après une petite recherche sur CPAN, HTML::Tidy paraît l'outil idéal pour vérifier la syntaxe d'un source HTML, et l'indenter. Tout d'abord, il faut organiser le site pour que tous les composants héritent d'un composant racine, que nous appelons tidy.html. Cela peut se faire à l'aide d'un autohandler (voir plus bas), ou bien grâce à l'orienté objet (expliqué dans le prochain article). Ce composant indente et analyse le contenu avant qu'il ne soit retourné à l'internaute. Voici le code commenté. Il illustre l'utilisation des balises <%once>, <%init> et <%filter>.

  %# Nous allons utiliser un objet tidy valable pour toute la durée de vie du
  %# processus alloué par Apache. %once est idéal pour une telle déclaration
  
  <%once>
    # déclaration de l'objet d'indentation du code
    use HTML::Tidy;
    my $tidy;
  </%once>
  
  %# Au début de l'appel du composant, il nous faut affecter $tidy, s'il n'est
  %# pas déjà défini.

  <%init>
    # initialisation si besoin est, pour être fork-safe
    if (!defined $tidy) {
      $tidy = HTML::Tidy->new( { config_file => '/tmp/tidy.cfg' } );
    }
  </%init>

  %# On applique un filtre pour auto-indenter le html qui sort.
  %# Pour cela, on utilise la section %filter, qui est appelée en
  %# fin de calcul du composant.  
  <%filter>
    # récupération du HTML
    my $html = $_;
    # on nettoie la sortie du composant
    $html = $tidy->clean($html);
    
    # s'il y a des erreurs ou des avertissements, on les affiche à la fin
    if ($tidy->messages) {

	# récupération des erreurs ou avertissements
        my @messages = $tidy->messages();
        
        # On ajoute les erreurs et avertissements HTML à la fin du contenu
	# si le _type du message est à 1, c'est un warning, sinon une erreur
        $html .= '<br><br><hr size=2 noshade>html warnings/errors : <br>' .
          join("\n<br>", map  {
              ($_->{_type} ? 'warning : ' : 'error : ') . $_->as_string;
          } ($tidy->messages)) . '<br><br>';
    }

    $_ = $html;

    # on réinitialise l'objet tidy
    $tidy->clear_messages();
  
  </%filter>

$r et $m : Deux couteaux suisses

Il est temps de présenter rapidement les deux objets principaux accessibles dans l'environnement Mason : $r et $m. Ces deux objets sont globaux, et utilisables partout sans avoir besoin de les déclarer. En réalité, il existe des exceptions, mais elles sont très rares. On peut considérer qu'on a accès à $r et $m dans toute situation.

$r

$r est l'objet request de mod_perl. Il permet un accès à l'API Perl d'Apache, ce qui est la grande force de mod_perl. Ainsi on pourra utiliser :

  $r->uri;            # L'URI HTTP (équivalent à l'URL dans bien des cas)
  $r->header_in(..);  # permet de récupérer les entêtes HTTP
  $r->content_type;   # permet de récupérer ou de changer le content-type
  $r->header_out(..); # permet de récupérer ou de changer un entête en sortie

$m

$m est un accès à l'API Mason. Ainsi, $m offre un accès au mécanisme de recherche et d'appel aux composants, permet d'accéder aux paramètres en cours et de les modifier. On peut même créer une requête Mason de toutes pièces, et déclencher un appel de composant dessus.

$m est une instance de la classe HTML::Mason::Request, et son API est conséquente. Une partie est la plus souvent utilisée, il s'agit de HTML::Mason::Component, qui concerne les composants. Comment travailler sur un composant ? $m permet d'obtenir une instance de HTML::Mason::Component facilement :

Une fois le composant récupéré, il est possible de travailler avec. On peut récupérer son chemin, ses attributs, ses drapeaux. On peut également savoir si une méthode existe et l'appeler, etc.

Nous allons voir rapidement comment utiliser certaines méthodes de $m dans les chapitres sur les dhandlers et les autohandlers.

Les handlers

le dhandler

Dans le chapitre sur « Les composants Mason », nous avons expliqué rapidement comment était choisi le composant « top-level ». Le principe de base est simple : à un composant est associé un fichier du même nom. Cependant, que se passe-t-il si le fichier composant n'existe pas ? Avant de renvoyer une erreur, Mason parcours le répertoire du composant et ses répertoires parents à la recherche d'un fichier spécial appelé dhandler. Dhandler veut dire "default handler", "gestionnaire par défaut".

Dès qu'un dhandler est trouvé, Mason l'exécute et arrête sa recherche. Un dhandler est un composant comme un autre, sauf qu'il peut utiliser à son avantage $m->dhandler_arg. Cette valeur de l'objet Mason ($m), contient le chemin vers le composant « top-level » qui n'existait pas.

Reprenons l'exemple donné dans le chapitre sur les composants, avec l'URL

  http://www.siteweb.com/produits/cuillere.html?modele=7

Nous pouvons créer un dhandler pour gérer le cas d'une demande d'un produit qui n'existe pas, et afficher un message d'erreur plus humain qu'un 404. Pour cela nous allons créer un dhandler dans le répertoire produit, qui affichera simplement que le produit n'existe pas. Voici le code de /produit/dhandler :

  <html>
  <body>
  <b>Il n'y a pas de <% $produit %>.</b>
  </body>
  </html>

  <%init>
  # on récupère le chemin complet (absolu) du produit demandé
  my $arg = $m->dhandler_arg;
  # on prend le dernier élément du chemin, donc le nom du composant demandé
  my $produit = (split('/', $arg))[-1];
  # on enlève le suffixe .html, ce qui donne le nom du produit
  $produit =~ s/\.html$//;
  </%init>

Avec ce dhandler, lors de la requête, Mason renverra :

  Il n'y a pas de cuillère.

Le dhandler peut donc être utilisé comme reprise sur erreur, mais nous allons voir qu'il est très intéressant de l'utiliser pour appeler le contenu adéquat. Il suffit de choisir une convention dans la syntaxe des URL, et d'utiliser le dhandler pour interpréter l'URL comme un ordre. L'exemple classique est celui du blog. Un blog est une collection de billets (petits textes) classés, qu'on peut atteindre par date ou par sujet par exemple. Les billets du blog sont stockés dans une base de données. Pour simplifier, on considérera que les billets sont stockés dans une unique table, avec une colonne pour la date et une pour le sujet du billet.

Nous pouvons convenir d'une convention d'URL comme suit :

  http://www.siteweb.com/blog/par_date/20060701
  http://www.siteweb.com/blog/par_sujet/informatique

Dans le répertoire /blog, nous allons écrire un dhandler qui interprétera l'URL suivant cette convention : le premier élément du chemin doit être /blog, le second indique sur quelle colonne de la table on filtre, et le troisième élément du chemin indique la valeur du filtre. Voici à quoi le dhandler ressemblera :

  <html>
  <body>
  <% $contenu %>
  </body>
  </html>
  
  <%init>
  # on considère qu'une connexion $DBH à la base de données a été faite
  # on récupère le chemin
  my $arg = $m->dhandler_arg;
  # on en fait un tableau en séparant sur /
  my @args = split('/', $arg);
  # on vérifie que le premier élément du chemin est /blog
  shift @args eq 'blog' or $m->decline;
  # on prend les deux prochains éléments
  my ($colonne, $valeur) = @args;
  # on exécute la requête dans la base de donnée
  my $sth = $DBH->prepare( qq{SELECT contenu FROM blog WHERE ? = ? } );
  $sth->execute($colonne, $valeur);
  my $lignes = $sth->fetchall_arrayref;
  # S'il n'y a pas de billet correspondant, on renvoie un 404
  @$lignes or return 404;
  # à partir du résultat de la requête, on construit $contenu
  my $contenu = ... 
  </%init>

Ce mécanisme permet d'accéder de manière souple et puissante au contenu du blog. Si un jour vous ajoutez une colonne dans la table blog, aucune modification n'est à faire sur le code Mason, pour pouvoir l'utiliser dans le site web.

Les lecteurs attentifs auront noté l'utilisation de $m->decline. Cette commande permet de dire que ce handler ne sait pas gérer la requête actuelle, et que Mason doit continuer à chercher un autre handler qui saura traiter la requête. Alors que return 404 est définitif.

Pour conclure, le dhandler est très similaire à AUTOLOAD en Perl. Il permet d'intercepter une requête a priori mauvaise, et d'effectuer une action à la place.

Autohandler

Une fois le principe du dhandler compris, il n'est pas difficile de comprendre ce qu'est un autohandler. Son nom veut dire "automatic handler", "gestionnaire automatique", et il fonctionne par répertoire. Lorsque Mason a trouvé le composant « top-level », il vérifie juste avant de l'exécuter s'il existe un fichier appelé autohandler dans le répertoire. Si c'est le cas, il l'exécute à la place, avec une petite subtilité : le composant sera ajouté à la pile d'appel.

Le fichier autohandler est un composant classique, mis à part le moment où il est appelé. Prenons l'URL d'exemple

  http://www.siteweb.com/produits/cuillere.html?modele=7

le composant top-level est cuillere.html, dans le répertoire /produits. S'il existe un fichier /produits/autohandler, il va être exécuté à la place de cuillere.html. Cependant, cuillere.html va être ajouté dans la pile d'appel de Mason. Ainsi, si l'autohandler fait appel à $m->call_next, alors le composant cuillere sera exécuté, puis Mason redonnera la main à l'autohandler.

L'intérêt immédiat est la possibilité d'encapsuler tous les composants d'un répertoire. Voici un autohandler trivial, qui ajoute un en-tête et un bas de page à tous les composants situés dans le répertoire :

  <html>
  <head><title>Mon site web fantastique</title></head>
  <body>
  
  % $m->call_next; # ceci appelle le composant originel
  
  <hr />
  Ceci est exemple de bas de page.
  </body>
  </html>

L'autohandler permet également de factoriser du code. Par exemple, sur un site web utilisant beaucoup de connexions à une base de données, chaque composant peut vouloir vérifier qu'il dispose d'une connexion à cette base, et la créer si besoin est. Au lieu d'écrire le code dans chaque composant, on peut le factoriser dans un autohandler.

Les autohandlers sont aussi utilisés pour inclure automatiquement des composants Mason, des modules Perl, ou même du javascript. Dans un site web utilisant beaucoup la librairie javascript Prototype, il sera intéressant d'écrire un autohandler comme suit :

  <html>
  <head>
    <title>Titre du site web</title>
    <script src="/chemin/de/prototype-1.4.0.js"></script>
  </head>
  <body>

  % $m->call_next; # ceci appelle le composant originel

  </body>
  </html>

En essayant d'élargir le concept, on peut utiliser une combinaison d'un dhandler et d'un autohandler, ce qui permet de déterminer un composant top-level en fonction de règles propres au site, et d'hériter du autohandler certaines actions. On peut également avoir plusieurs autohandlers, qui seront appelés successivement. Cependant ce fonctionnement est assez difficile à gérer, et on atteint assez vite ses limitations.

Ces problèmes sont résolus par l'utilisation de la programmation orientée objet de Mason, que nous aborderons dans le prochain article.

Conclusion

C'est la fin de cet article, mais pas de l'aventure ! Nous venons de voir les bases de Mason, qui devraient vous permettre de vous lancer dans l'implémentation d'une première application Web dynamique. Dans le prochain article, nous aborderons entre autres les fonctionalités Orientées Objects de Mason, l'utilisation du système de cache intégré, et nous testerons un module Ajax.

Liens

MasonHQ - http://www.masonhq.com

Vue Mason pour Catalyst - http://search.cpan.org/~ank/Catalyst-View-Mason

Jifty - http://jifty.org

Auteur

Damien Krotkine <dams@zarb.org> - Paris.pm

Notes

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]