[couverture de Linux Magazine 93]

Mason - Deuxième partie

Article publié dans Linux Magazine 93, avril 2007.

Copyright © 2007 - Damien Krotkine.

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

Chapeau

Voici le deuxième article de la série sur Mason, un framework basé sur mod_perl, qui permet de créer des sites web dynamiques. Après avoir vu les bases de Mason dans l'opus précédent, nous allons aborder deux sujets importants : la programmation Orientée Objet et la mise en place du système de cache. Nous terminerons par un exemple d'utilisation du module Ajax.

Mason orienté objet

Introduction

La POO en Perl

Voici quelques références pour apprendre la programmation orientée objet en Perl :

  • En français : l'article La programmation objet en Perl par Sylvain Lhullier, dans GLMF n°47, février 2003

  • En anglais : l'excellent Tom's object-oriented tutorial accessible sur toute distribution linux qui se respecte via : man perltoot

Mason implémente des fonctionnalités qui permettent de faire de la programmation orientée objet. Cette implémentation est critiquable sur le plan de sa rigueur, car tous les paradigmes ne sont pas présents. Cependant, elle est largement suffisante pour proposer l'encapsulation, l'héritage et le polymorphisme. Ces concepts permettent de repenser totalement le design technique d'un site web et de son implémentation.

Voici la définition de la programmation objet donnée par Wikipédia :

« La programmation orientée objet est une façon d'architecturer une application informatique en regroupant les données et les traitements de ces dernières au sein des mêmes entités, les objets. »

Le lecteur sans aucune connaissance en programmation Orientée Objet pourra se référer aux liens proposés. Une connaissance basique, que ce soit en Perl ou dans un autre langage, est suffisante pour la suite de cet article.

Le concept

Attention, ici nous parlons d'autre chose que de l'orientation objet en Perl. En effet, il y a de fortes chances pour qu'un site web Mason utilise un jeu de modules Perl ad hoc, qui peuvent utiliser de l'orienté objet. On peut également, dans les balises Mason, manipuler des objets Perl. Mais ce dont nous parlons ici, c'est de considérer le composant Mason comme un objet à part entière.

Abus de langage

Il est courant d'utiliser le terme composant ou objet pour parler de la même chose : une instance d'un composant Mason, ou l'objet qu'il représente. Il est également courant de parler de classe ou de composant, car c'est le source du composant qui décrit la classe de l'objet. Rassurez-vous, le contexte permet généralement de lever l'ambiguïté sur le sens du mot composant.

Utilisons un exemple simple pour expliciter le concept. Considérons le site web d'une entreprise, qui contient deux parties. Une d'elles est la présentation des employés, l'autre est la présentation des produits. En plus, il y a une page légale sur l'entreprise.

Un exemple

Analyse du problème

Toutes les pages de ce site web contiennent un en-tête, un bas de page, et un titre. Les pages des employés sont structurées de la même manière : le nom de l'employé, sa photo, et sa fonction. Quant aux pages produits, elles contiennent le nom du produit, sa photo et son prix.

Ainsi, on peut en déduire les classes suivantes :

Le titre, le nom, la photo et la fonction sont des attributs, car ils ne nécessitent pas de traitements. En revanche, l'en-tête, le bas de page et le prix seront des méthodes. On peut imaginer récupérer le prix de chaque produit dans une base de données, par exemple.

Structure des fichiers

Après avoir fait cette analyse, il est maintenant possible de lister tous les composants (donc les classes) Mason du site, et d'avoir la structure des fichiers du site web :

  /index.html                           # la page d'entrée du site, hérite de page_web.html
  /pageweb.html                         # implémente "titre", "entete", "basdepage"
  /pageweb/legal.html                   # la page d'informations légale du site, hérite de pageweb
  /pageweb/nom_photo.html               # hérite de pageweb, implémente "nom" et "photo"
  /pageweb/nom_photo/employe.html               # hérite de nom_photo, implémente "fonction"
  /pageweb/nom_photo/employe/jean.html          # page d'un employé
  /pageweb/nom_photo/produit.html               # hérite de nom_photo, implémente "prix"
  /pageweb/nom_photo/produit/cuillere.html      # page d'un produit
  /pageweb/nom_photo/produit/couteau.html       # page d'un produit

/pageweb.html, /pageweb/nom_photo.html, /pageweb/nom_photo/employe.html et /pageweb/nom_photo/produit.html ne doivent pas être appelées directement. La structure des fichiers rappelle celle des modules CPAN, c'est voulu mais ce n'est pas obligatoire. C'est une convention que nous recommandons de suivre, sous peine de se perdre facilement dans le code du site web.

Chaîne d'appel

Le principe de l'orienté objet Mason est le suivant : lors de l'appel de /pageweb/nom_photo/produit/cuillere.html, une chaîne d'appel est créée. Mason la détermine en partant du composant appelé, et en remontant suivant l'héritage, puis la redescend pour générer l'affichage. Lors de la remontée, Mason agrège les propriétés de l'objet avec ses parents. Une fois arrivé au sommet de la chaîne, il peut la redescendre, et afficher le contenu des composants, car il a toutes les informations nécessaires. Voici la partie descendante de la chaîne :

  pageweb.html, nom_photo.html, produit.html, cuillere.html

Attention, contrairement à la programmation orientée objet en Perl, ici tous les éléments de la chaîne d'héritage seront appelés. Il n'y a pas besoin d'appeler son parent.

Par contre, un composant doit spécifiquement appeler ses éventuels enfants. En effet, les composants enfants insèrent du HTML, et il est du ressort du parent de spécifier où le HTML des enfants va apparaître, en utilisant cette syntaxe [1] :

  %# on appelle le reste de la chaîne
  % $m->call_next;

Nous allons implémenter tous les composants de ce site web, mais d'abord, voyons quelle est la syntaxe des différents aspects de la programmation orientée objet.

Attributs, méthodes, héritage, SELF, PARENT

Les attributs et les méthodes sont des éléments indispensables à la programmation orientée objet. Il faut également avoir un moyen de spécifier l'héritage d'un objet par rapport à un autre. Voyons comment tout cela est fait dans Mason :

Attributs

Les attributs permettent d'accrocher une caractéristique simple à un objet. La syntaxe est la suivante :

  <%attr>
  nom => 'Dupuis'
  prenom => 'Jean'
  tableau => [1, 2, 3]
  gestionnaire => sub { ... }
  </%attr>

Il est possible de stocker n'importe quel type Perl dans un attribut, même une fonction anonyme.

Voici comment récupérer la valeur d'un attribut, dans une section <%perl> :

  $composant->attr('titre')

D'où vient $composant ? Plusieurs méthodes existent pour accéder à un objet composant dans une section <%perl> :

Méthodes

Les méthodes permettent d'effectuer des tâches sur l'objet. Ici on parle de méthode Mason, et non Perl, car c'est le composant Mason qui est considéré comme objet. Il nous faut donc une méthode Mason faisant partie du composant.

  <%method afficher_titre>
  Ceci est le titre de la page
  % my $foo = 2 + 2
  <% $foo %>
  </%method>

La syntaxe est simple, et permet d'encapsuler du code Mason dans une méthode, qu'il sera possible d'appeler ultérieurement. Appeler une méthode Mason est très similaire à l'appel d'un composant. Il suffit de rajouter le suffixe :methode :

  <& composant:methode &>
  <& /produits/cuillere:afficher_titre &>

Héritage

L'héritage se spécifie dans un composant par l'utilisation de la section <%flag> :

  <%flags>
  inherit => '../parent.html'
  </%flags>

Cela permet de spécifier que le composant en cours hérite de ../parent.html. Mason ne supporte pas l'héritage multiple, contrairement à Perl. Certains diront que c'est une fonctionnalité, et non une limitation :).

SELF

Il est intéressant d'avoir la main sur soi-même lorsqu'on est dans le code de l'objet. C'est ce qu'on appelle généralement self.

En Perl, ça sera une variable $self. En Mason, SELF est le composant lui-même (en fait son instance). On l'obtient grâce à une méthode de $m :>

  my $self = $m->base_comp;

Comment fait-on, dans un composant, pour appeler une de ses méthodes ? Mason propose un raccourci lors des appels de méthodes :

  <& SELF:entete &>

Ce code appelle la méthode entete du composant courant.

PARENT

De la même manière que pour SELF, Mason propose :

  my $parent = $m->current_comp->parent

pour manipuler le composant parent, et

  <& PARENT:methode &>

pour appeler une méthode du parent.

Implémentation

Bien, maintenant que nous avons analysé notre exemple de site web, et que nous avons les informations pour implémenter les aspects objets de nos composants, nous pouvons commencer à les implémenter.

On commence par /pageweb.html :

  <html>
  <head>
  %# Le titre est affiché à partir de l'attribut 'titre'
    <title><% $self->attr('titre') %></title>
  </head>
  <body>
  %# l'entete, implémenté plus bas 
  <& SELF:entete &>

  %# on appelle le reste de la chaîne (et donc les enfants de ce composant)
  % $m->call_next;

  %# le bas de page
  <& SELF:footer &>
  </body>
  </html>

  <%init>
  %# on a besoin de $self pour accéder aux attributs
    my $self = $m->base_comp;
  </%init>

  <%attr>
  titre => 'titre par défaut'
  </%attr>

  <%method header>
  %# voici un header simpliste : on affiche le titre
  <h2><% $self->attr('titre') %></h2>
  </%method>

  <%method footer>
  %# un bas de page vide
  </%method>

On peut tout de suite coder la page /pageweb/legal.html :

  <%flags>
  %# ce composant hérite de pageweb
  inherit => '../pageweb.html'
  </%flags>

  <%attr>
  titre => 'Mentions légales'
  </%attr>

  Corps de la page : ici on mentionne le copyright, etc.

On peut voir que ça permet de raccourcir la taille du code, et d'être donc plus maintenable. Passons maintenant à /pageweb/nom_photo.html :

  <%flags>
  inherit => '../pageweb.html'
  </%flags>
  
  %# on affiche le nom et l'image de la photo
  <h2><% $self->attr('nom') %>
  <img src='/images/<% $self->attr('image') %>'>

  %# on appelle le reste de la chaîne
  % $m->call_next;

  <%init>
    my $self = $m->base_comp;
  </%init>

Voyons le source de /pageweb/nom_photo/employe.html qui doit implémenter la fonction de l'employé :

  <%flags>
  inherit => '../nom_photo.html'
  </%flags>
  
  <%attr>
  titre => 'Page des employés (titre par défaut)'
  </%attr>

  <% $self->attr('nom') %> a pour fonction <% $self->attr('fonction') %>.

  %# on appelle le reste de la chaîne
  % $m->call_next;

  <%init>
    my $self = $m->base_comp;
  </%init>

On peut ajouter la page de l'employé Jean /pageweb/nom_photo/employe/jean.html :

  <%flags>
  inherit => '../employe.html'
  </%flags>
  
  <%attr>
  nom => 'Jean'
  image => 'jean.jpg'
  fonction => 'directeur général'
  </%attr>

  Ceci est la page de Jean.

Voici le code de /pageweb/nom_photo/produit.html. Le prix sera extrait d'une base de données.

  <%flags>
  inherit => '../nom_photo.html'
  </%flags>
  
  <%attr>
  titre => 'Page des produits (titre par défaut)'
  </%attr>

  <% $self->attr('nom') %> a pour prix <& SELF:get_prix &>

  %# on appelle le reste de la chaîne
  % $m->call_next;

  <%init>
    my $self = $m->base_comp;
  </%init>

  <%method get_prix>
  %# par défaut on va chercher le prix dans une base de données, et l'afficher
  ...
  </%method>

On peut maintenant définir les produits, tout d'abord /pageweb/nom_photo/produit/cuillere.html

  <%flags>
  inherit => '../produits.html'
  </%flags>
  
  <%attr>
  nom => 'Cuillere'
  image => cuillere.jpg'
  </%attr>

  Ceci est une cuillère. Son prix est extrait de la base de données

Maintenant faisons pareil pour /pageweb/nom_photo/produit/couteau.html. Cependant ici, le prix sera fixe, et non pas récupéré dans la base de données. Pour cela, on surcharge la méthode get_prix de produit.html

  <%flags>
  inherit => '../produits.html'
  </%flags>
  
  <%attr>
  nom => 'couteau'
  image => 'couteau.jpg'
  </%attr>

  <%method get_prix>
  57
  </%method>

La programmation orientée objet de Mason est très intéressante, car elle permet d'utiliser le même mode de programmation entre le frontal et le dorsal (qui est très souvent développé en orienté objet) d'un site web. Cependant cette méthode nécessite un petit temps d'adaptation par rapport à la programmation orientée objet de Perl.

Il est généralement de bonne augure de réfléchir à la réécriture d'un site web existant en utilisant cette technique. Elle permet dans bien des cas de pointer des erreurs de conception, et de simplifier la gestion et l'amélioration future du site.

Système de cache

Théorie

La force des frameworks web dynamiques est de générer du contenu à la volée. Ainsi le site web vit de lui-même, se met à jour tout seul en fonction de différents flux de données (par exemple une base de données, ou bien une livraison électronique de nouvelles).

Cependant, cette puissance a un prix : cela coûte de la ressource matérielle, notamment du temps processeur. Et ceci peut être ennuyeux. En effet, si un serveur web n'arrive pas à générer les pages web aussi vite que les requêtes arrivent, le site peut devenir inaccessible.

Pour essayer d'éviter ce problème, on peut spécifier au framework qui fait tourner le site web, que les pages web de tel répertoire sont statiques, donc elles doivent être livrées telles quelles, sans être analysées ou générées. En fait, on dit au framework d'ignorer ces pages. De cette manière, il s'occupe des pages dynamiques, et laisse le serveur web faire son travail pour les pages statiques, ce qui est bien plus rapide.

Pourtant, cette manière de faire ne résout pas vraiment le problème de ressources des serveurs web typiques, car la plupart des sites ne contiennent que très peu de pages vraiment statiques.

Depuis plusieurs années, la plupart des sites web conséquents utilisent un procédé simple à mettre en œuvre, et efficace, pour diminuer les besoins en ressources du serveur : il s'agit du système de cache. Le principe est simple : au lieu de générer une page du site pour chaque visiteur, on la crée une seule fois, et on garde le HTML généré. Ce contenu généré (ou statique) est alors utilisé pour les autres visiteurs de cette page. Ainsi, on peut allier la puissance d'un site dynamique à la performance d'un site statique.

Mettre en place un système de cache amène son lot de problèmes, dont voici une liste des plus importants :

Le problème crucial est sans doute le dernier mentionné : il est important de savoir si cela vaut le coup d'activer un système de cache pour telle ou telle page du site web. Pour cela, il faut pouvoir juger du degré de dynamisme de la page web : en clair, plus une page change souvent de contenu, moins il sera rentable de la mettre en cache.

On a souvent recours à une classification erronée des pages d'un site web : d'un côté les pages dynamiques, qui nécessitent d'être générées à la volée, et de l'autre les pages statiques, qui ne changent jamais sur le site web. En analysant plus finement le contenu des sites web, on se rend compte qu'il faut prendre en considération sa fréquentation, et classer les pages en fonction de leur fréquence de changement par rapport à la fréquentation.

Par exemple, si en moyenne, une page A change de contenu une fois par heure, et que 50 personnes la consultent toutes les heures, on obtient le ratio changement de contenu par rapport à la fréquentation de 1/50 pour une heure, ce qui est très faible. Cette page peut être considérée comme peu dynamique, et il sera rentable de la mettre en cache.

Une autre page B, qui change tous les jours, mais qui n'obtient un taux de fréquentation que de 5 personnes par jour, aura un ratio de 1/5 pour un jour, donc de 4.8 pour une heure. Comparativement à la page A, la page B est en fait très dynamique, alors que son contenu ne change qu'une fois par jour.

Malgré les apparences, il est plus intéressant de mettre en cache la page A qui change toutes les heures, que la page B, qui change tous les jours.

Mise en œuvre

Le système de cache de Mason utilise en fait le module Cache::Cache de DeWitt Clinton. Plus précisément, Mason utilise un sous-module. Par défaut, ce sera Cache::FileCache. Ce module stocke les données à mettre en cache dans des fichiers, en les triant par nom de composant Mason. Bien entendu, il est possible de changer le type de système de cache, et d'autres options, comme expliqué un peu plus loin. Mais la configuration par défaut est un bon moyen de tester et mettre en œuvre le système de cache de Mason. Par défaut donc le module de mise en cache utilisé est Cache::FileCache, et le cache est nommé cache dans le répertoire de données de Mason.

Algorithme de mise en cache

Le principe est très simple, le système de cache permet d'associer une clef à du contenu, et d'effectuer des actions sur ce couple :

Il est important de noter que les couples clef/contenu sont propres à chaque composant. Ainsi, on est sûr de ne pas écraser les données de cache d'un autre composant.

Avant d'écrire du code, il est préférable de réfléchir à l'algorithme de mise en cache, en se posant quelques questions :

Grâce aux réponses à ces questions, on peut alors mettre au point un algorithme de mise en cache, qui ressemblera le plus souvent à ceci :

  clef = Generer_clef()
  contenu_statique = Recuperer_du_cache(clef)
  SI contenu_statique == vide ALORS
    contenu_statique = Generer_le_contenu()
    condition = Generer_condition_d_expiration
    Stocker_dans_le_cache(clef, contenu_statique, condition)
  FIN
  Renvoyer(contenu_statique)

Pas de magie noire : si un contenu statique existe, on le renvoie directement, sinon on le crée avec la bonne clef et la condition d'expiration. Cette dernière peut être temporelle (« ce contenu a une durée de vie de 5 minutes »), ou bien conditionnelle (« ce contenu n'est plus valable si une news est créée entre-temps »).

Implémentation

L'objet de cache est accessible en utilisant :

    $m->cache

Cet objet implémente plusieurs méthodes, que nous présentons dans ce morceau de sources :

    my $contenu = $m->cache->get('clef');
    if (!defined($contenu)) {
        ... 
    	$contenu = ...;
    	...
        $m->cache->set('clef', $contenu, $condition_optionnelle);
    }

Ainsi, avec les méthodes set et get, on peut implémenter un système de cache fonctionnel.

Voyons maintenant un exemple réel. Imaginons un site web d'information avec une page de recherche sur les archives. Cette page web est implémentée par un composant qu'on appelle pour l'occasion recherche.html. Ce composant prend en argument la requête de l'utilisateur. Les brèves sont stockées dans une base de données, et une méthode Cherche_dans_DB(requete) permet de lancer une recherche dans la base. Cette méthode renvoie une référence sur une liste de liens vers les brèves qui correspondent. Cependant, cette recherche est assez coûteuse en ressources CPU, et serait avantageusement épaulée d'une mise en cache des requêtes les plus courantes.

Nous allons écrire le code qui met en cache la requête et la liste des réponses associées. Ainsi, si la requête de recherche "perl" retourne une liste (A, B, C) de brèves qui traitent de ce merveilleux langage, cette liste sera stockée dans le cache. Cependant, lorsqu'une nouvelle brève D est ajoutée dans la base de donnée du site, le contenu du cache n'est peut-être plus valide. En effet, si la brève D répond aux critères de recherche (ici "perl"), la requête devrait renvoyer (A, B, C, D), or la réponse mise en cache ne contient pas D.

Un moyen simple de résoudre ce problème est d'invalider l'information mise en cache dès qu'une nouvelle est ajoutée à la base de donnée. Pour cela, on peut demander à la base de données le dernier ID des brèves, via une méthode Recupere_dernier_ID(). Si cet ID change, alors le contenu généré doit être invalidé. Comment implémenter cela ? Simplement en ajoutant cet ID à la clef du contenu mis en cache.

Voici un prototype du code de recherche.html :

  %# Composant recherche.html

  %# argument : la requête de recherche
  <%args>
  $requete
  </%args>

  <html>
  <body>
  Voici les résultats de la requête S<"<% $requete %>" :>
  <%perl>
  # construction de la clef, constituée de la requête, et du dernier ID
  my $dernier_id = Recupere_dernier_ID();
  my $clef = $requete . '|' . $dernier_id;

  # on teste s'il existe un contenu déjà en cache
  my $contenu = $m->cache->get($clef);
  if (!defined($contenu)) {
      $contenu = Cherche_dans_DB($requete);
      # on stocke le contenu avec un délai d'expiration de 10 jours
      $m->cache->set($clef, $contenu, '10days');
  }

  # ici, on a récupéré ou généré $contenu, qui est en fait une ref. sur liste
  my @liste = @$contenu;
  </%perl>
  %# on peut maintenant l'afficher en HTML
  <ul>
  % foreach (@liste) {
      <li><% $_ %></li>
  %}
  </ul>

  </body>
  </html>

Finalement, ce n'était pas si difficile. Cependant, on notera que l'on a mis un délai d'expiration arbitraire, 10 jours. On considère en effet qu'il est improbable qu'aucune nouvelle brève n'apparaisse en 10 jours. Au bout de 10 jours, le cache sera nettoyé [2]. Si jamais un contenu est effacé à tort, ce n'est pas grave, le pire qui puisse arriver est qu'il soit régénéré une fois pour rien tous les dix jours.

Expiration conditionnelle

Fixer un délai d'expiration arbitraire est une solution acceptable dans beaucoup de cas, mais nous allons examiner deux méthodes pour invalider des données. Tout d'abord, au lieu d'utiliser une expiration purement temporelle (ici, 10 jours), utilisons une clause d'expiration conditionnelle, avec expire_if.

expire_if prend en argument une méthode anonyme, qui est appelée avec l'objet cache comme unique paramètre. Si la méthode renvoie une valeur vraie, alors le contenu est invalidé, et effacé du cache.

Ici, nous allons tester si le dernier ID de la base de données est toujours le même. Grâce à expire_if, nous n'avons plus besoin de stocker l'ID dans la clef. Voici la partie Perl du source précédent, modifié pour l'occasion :

  <%perl>
  # construction de la clef, constituée de la requête uniquement
  my $clef = $requete

  # on teste s'il existe un contenu déjà en cache
  my $contenu = $m->cache->get($clef);
  if (!defined($contenu)) {
      $contenu = Cherche_dans_DB($requete);
      # on récupère le dernier ID
      my $dernier_id = Recupere_dernier_ID();
      # on stocke le contenu avec un test d'expiration sur l'ID
      $m->cache->set($clef, $contenu, expire_if => sub { Recupere_dernier_ID() != $dernier_id; });
  }

  # ici, on a récupéré ou généré $contenu, qui est en fait une ref. sur liste
  my @liste = @$contenu;
  </%perl>

Il faut bien comprendre que $dernier_id contient le dernier id au moment de la mise en cache initiale du contenu. Cet id sera ensuite comparé au dernier id courant lors de chaque appel de la sous-routine expire_if. En effet, la fonction anonyme sub { Recupere_dernier_ID() != $dernier_id; } est ce qu'on appelle une fermeture. Elle hérite du contexte dans lequel elle est créée, notamment des variables lexicales.

Invalidation externe

Il existe une dernière manière d'invalider du contenu dans le cache, en utilisant le module de cache utilisé par Mason (par défaut Cache::FileCache), dans un programme Perl, qui peut être à l'extérieur du site web.

Mason fournit un module utilitaire qui permet d'accéder facilement aux informations mises en cache par un composant. Il faut lui spécifier le nom du composant, et l'endroit où est stocké le cache de Mason (par défaut c'est le sous-répertoire cache de MasonDataDir, spécifié dans le fichier de configuration d'Apache [3]).

  # On charge les fonctions relatives au cache du module utilitaire
  use HTML::Mason::Utils qw(data_cache_namespace);

  # création d'un nouvel objet Cache
  my $cache = new Cache::FileCache( {
          namespace  => data_cache_namespace('/chemin/du/composant'),
          cache_root => '/var/cache/apache/cache'
  } );

  # Effacer une clef spécifique
  $cache->remove('clef1');

  # Effacer tout le cache pour ce module
  $cache->clear();

Les possibilités offertes par l'accès externe au système de cache sont multiples. Ainsi une tâche de nettoyage du cache peut être déclenchée à intervalle régulier (configuré dans la crontab du système par exemple), sans avoir besoin d'interagir avec mod_perl. On peut également déclencher l'effacement d'une clef du cache, lorsque la donnée associée est effacée de la base de donnée.

Configuration du système de cache

Par défaut, Mason utilise Cache::FileCache et les données du cache sont stockées dans data_dir/cache. Ces paramètres (et d'autres) peuvent être changés dans le fichier de configuration d'Apache, via MasonDataCacheDefaults :

  PerlSetVar MasonDataCacheDefaults "cache_class => MemoryCache"
  PerlAddVar MasonDataCacheDefaults "cache_depth => 2"
  PerlAddVar MasonDataCacheDefaults "default_expires_in => 1 hour"

Ajax et Mason

Introduction

Sur le site principal de Mason, on peut trouver des composants à télécharger et utiliser sur nos sites web. Il s'agit d'exemples de code, ou de composants génériques permettant de répondre à des problématiques fréquentes. L'un d'eux traite d'Ajax, il est accessible ici : http://www.masonhq.com/?Component:ajax

Il permet d'utiliser le principe d'Ajax sans écrire de JavaScript, ou alors très peu. Ce composant (appelé ajax) utilise la bibliothèque JavaScript Prototype pour gérer l'appel asynchrone, et il permet d'implémenter la réponse en Perl, et non pas en JavaScript. Voyons un exemple qui permet de détailler le fonctionnement de ce composant.

Tout d'abord, il nous faut télécharger ce module depuis http://www.masonhq.com/?Component:ajax, et le sauvegarder dans un composant Mason, par exemple à la racine du site web, /var/www/localhost/htdocs/mason.html. Ainsi il sera accessible à l'adresse http://www.siteweb.com/mason.html. Il faut également se procurer la librairie JavaScript Prototype depuis http://www.prototypejs.org/ et l'installer dans un répertoire du site web, par exemple /var/www/localhost/htdocs/js/prototype-1.4.0.js.

Pour illustrer l'utilisation de ces technologies, nous allons construire une page web qui permet d'afficher une image au hasard.

Récupérer une image aléatoire

Pour cela, il nous faut tout d'abord une banque d'images accessible sur le net, avec un lien permettant d'en afficher une au hasard. Vous connaissez sûrement le site web de l'excellent Ayo (http://www.73lab.com/), qui produit de très jolies images, fonds d'écrans et autres éléments graphiques. Son site web met à notre disposition une addresse spéciale, qui permet d'afficher une vignette de fonds d'écran au hasard : http://ayo73.free.fr/random_pic/random_pic.php3 [4]. Voici un exemple du contenu HTML renvoyé par cette page :

  <a href="http://www.73lab.com" border=0 target=new>
    <img
     src="http://ayo73.free.fr/random_pic/pics/05_ayodacode_epreuve_1280_1024_thumb.jpg"
     border=0 width=120 height=90
    >
  </a>

Il suffit de récupérer l'attribut src du tag img, et nous obtenons l'adresse d'une image prise au hasard. Voici le code que nous pouvons utiliser pour faire ce travail :

  use LWP::Simple;   # utilisation de modules perl 
  use URI;           # pour faciliter le travail

  # on charge l'URL du site d'Ayo, et on ne prend que l'attribut src du tag img
  my ($url_image) = (get("http://ayo73.free.fr/random_pic/random_pic.php3") =~ /img src="(.*?)"/);

  # on extrait le nom de l'image de son adresse
  my $nom_image = (URI->new($url_image)->path_segments())[-1];

  # enfin, on télécharge l'image, et on la place dans un repertoire accessible
  # en écriture par Apache.
  mirror($url_image, "/var/www/localhost/htdocs/images/$nom_image");

Ce code devra être placé dans une méthode, pour pouvoir être appelé facilement.

Page d'affichage HTML

Concernant notre page d'exemple à proprement parler, côté HTML, nous allons faire simple : un div, et un bouton, qui, lorsqu'il est cliqué, va télécharger une image au hasard et changer le contenu du div pour l'afficher. L'image va être téléchargée sur le serveur web, et une balise img pointant sur sa copie locale va être insérée dans la balise div. Voici le HTML de la page :

  <html>
  <head>
    <title>Test d'Ajax avec HTML::Mason</title>
  </head>

  <body>
    <h1>Test d'Ajax avec HTML::Mason</h1>
  <input type="button" value="image aléatoire">
  <br/>
  <br/>
  <div id="div_image">cliquez !</div>
  <br>

Associer les deux

À présent, il nous faut lier les deux parties, le HTML et le code qui récupère l'image. Lors d'un clic sur le bouton, il faut télécharger une nouvelle image, la stocker localement, et ajouter un tag <img> dans le <div>, qui est caractérisé par son identifiant "div_image".

Pour cela, quelques modifications sont à faire. Nous allons tout d'abord ajouter un événement sur le clic du bouton, qui effectue un appel au composant ajax.html. Cet appel spécifiera la méthode Mason à utiliser pour créer du HTML, et que ce HTML de retour doit être injecté dans le tag "div_image". Voici comment modifier le tag input :

  <input type="button" value="image aléatoire" 
   onClick="<& /ajax.html, comp=>'SELF:telecharge_image_aleatoire', update=>'div_image' &>;";>

L'option comp spécifie le composant et la méthode qu'il faut appeler lors du clic. Ici, nous indiquons que la méthode est telecharge_image_aleatoire, qui est dans le composant courant.

L'option update permet de spécifier dans quel tag le HTML de retour doit être injecté. Nous utilisons div_image.

Pour que le JavaScript généré par le composant ajax fonctionne correctement, il faut que la bibliothèque Prototype soit chargée. Il faut donc également ajouter une ligne qui inclut cette librairie.

Nous pouvons à présent écrire l'intégralité de notre page web de test, en incluant le code Perl de récupération d'une image au hasard. Voici le source final :

Le code final

L'option throbber

Elle permet d'afficher une image animée pendant le rafraîchissement. Cette image GIF doit être placée par défaut à la racine du site web, et s'appeler throbber.gif. Lors de l'appel Ajax, l'élément du DOM sera remplacé par ce GIF animé pendant le temps de chargement.

  %# code de la page test_ajax.html

  %# méthode qui incorpore le code Perl vu précédemment
  <%method telecharge_image_aleatoire>

  <%perl>
    use LWP::Simple;
    use URI;
    my ($url_image) = (get("http://ayo73.free.fr/random_pic/random_pic.php3") =~ /img src="(.*?)"/);
    my $nom_image = (URI->new($img_url)->path_segments())[-1];
    mirror($img_url, "/var/www/localhost/htdocs/images/$nom_image");
  </%perl>

  %# ici nous retournons le code HTML qui affiche l'image locale
  <img src='<% "/images/$nom_image" %>'/>

  </%method>

  <html>
  <head>
    <title>Test d'Ajax avec HTML::Mason</title>
    <script src="/var/www/localhost/htdocs/js/prototype-1.4.0.js"></script>
  </head>

  <body>
    <h1>Test d'Ajax avec HTML::Mason</h1>
  <input type="button" value="image aléatoire" onClick="
  <& /ajax.html, comp=>'SELF:telecharge_image_aleatoire', throbber => 1, update=>'div_image' &>;
  ";>
  <br/>
  <br/>
  <div id="div_image">Cliquez !</div>
  <br>

Voici une capture d'écran [5] du résultat :

[capture d'écran de l'utilisation d'Ajax en Mason]
Utilisation d'Ajax en Mason

Le module ajax utilisé présente plusieurs avantages. Tout d'abord il permet de modifier le contenu de la page courante sans la recharger. Mais en plus, il permet de le faire avec pratiquement pas de JavaScript. Enfin, la syntaxe Mason permet de rassembler le code à exécuter sur le serveur et l'affichage du contenu dans un même composant, ce qui le rend plus simple à écrire et à maintenir.

Conclusion

Nous avons examiné quelques fonctionnalités avancées de Mason dans cet article, qui en font un framework complet. Mason dispose également d'un certain nombre de module d'extensions, touchant à des sujets variés, disponibles sur le CPAN, dans le groupe MasonX. Dans le prochain article, nous verrons quelques-uns de ces modules utilisés dans un cas concret.

Liens

MasonHQ - http://www.masonhq.com

Le composant Ajax - http://www.masonhq.com/?Component:ajax

La librairie Prototype - http://www.prototypejs.org

Le site d'Ayo - http://www.73lab.com/

Auteur

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

Notes

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