Article publié dans Linux Magazine 93, avril 2007.
Copyright © 2007 - Damien Krotkine.
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.
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.
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.
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 :
page_web
C'est la page de base, qui contient un titre, un en-tête et un bas de page
nom_photo
Cette page hérite de page_web
, elle présente un nom et une photo
employe
Elle hérite de nom_photo
, et en plus implémente la fonction de l'employé
produit
Elle hérite de nom_photo
, elle implémente le prix
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.
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.
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.
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 :
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->current_comp
Renvoie le composant courant.
$m->fetch_comp(chemin)
Renvoie le composant correspondant au chemin.
Voir plus bas.
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 &>
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.
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.
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 :
Mettre en place un système de cache coûte toujours un peu de temps de configuration. Même s'il est extrêmement réduit avec Mason, il peut être utile de prendre en compte ce coût.
Un système de cache prend de la place sur le disque dur du serveur, et quelque fois augmente la consommation mémoire lors de la génération du contenu statique. Pour certains sites web où tout est mis en cache, les quantités de données stockées sur le disque dur peuvent être énormes.
Le système de cache doit vérifier quand le contenu statique généré doit être révoqué. Si une page web affiche la date du jour, on ne peut la mettre en cache qu'un jour, il faudra la régénérer le jour suivant, la date ayant changé. Pour des pages très dynamiques, mettre en place un système de cache ne sera pas d'une grande utilité, et pourra complexifier inutilement la configuration du site web.
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.
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.
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 :
Stocker un nouveau contenu identifié par une clef. On peut stocker n'importe quel objet Perl : une chaîne, un tableau, etc. Le contenu stocké a une durée de vie, qui peut être infinie ou conditionnée. C'est ce qu'on appelle l'expiration.
Récupérer un contenu grâce à sa clef. On peut tester si la clef existe, et si le contenu existe.
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 :
Pour un composant donné, où écrire le code ?
Il ne faut pas vouloir tout mettre en cache. Comme vu dans la section précédente, il est important d'implémenter la mise en cache dans les composants qui sont le plus statique. A l'intérieur du composant, on se placera généralement au début, pour pouvoir court-circuiter la génération du contenu de la page web.
Quelle clef choisir ?
Il faut une clef pour y associer le contenu généré du composant. Cette clef ne peut pas être arbitraire. Elle doit correspondre à un schéma qui identifie de manière unique la manière dont le composant a été appelé.
Quelle raison invalide le contenu ?
L'avantage de la mise en cache est qu'elle est très performante pour les pages peu dynamiques, mais elle n'empêche pas le contenu de la page de changer. Il faut à présent caractériser ce qui fait que le contenu du composant va changer, c'est à dire, ce qui fait que le contenu mis en cache expire, et doit être éliminé. Cela peut être une notion purement temporelle (une page de météo mise à jour toutes les 24 heures), ou cela peut être un test faisant intervenir des éléments externes (comme une base de données).
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 »).
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.
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.
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.
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"
cache_class
permet de changer la classe utilisée pour la mise en cache.
File::Cache
, MemoryCache
sont des exemples. NullCache
peut être
utilisé, cela permet de désactiver le cache totalement, sans changer une ligne
de code, très utile pour trouver un bug, sans que la mise en cache interfère.
cache_depth
permet de préciser la profondeur maximale des composants
qui vont pouvoir utiliser le système de cache.
default_expires_in
permet de préciser le délai d'expiration par défaut
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.
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.
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>
À 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 :
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 :
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.
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.
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/
[1] Voir l'article précédent pour une description de $m
.
[2] Attention, il ne s'agit pas du cache complet qui sera nettoyé, mais seulement l'élément du cache concernant la requête dont on parle. Ce nettoyage à granularité fine permet d'éviter au serveur web d'effectuer une grande quantité d'accès disque au même moment, comme cela aurait été le cas si l'ensemble du cache aurait été invalidé au bout de 10 jours.
[3] Voir dans l'article précédent la partie sur la configuration d'Apache
[4] On pardonnera à Ayo d'utiliser du PHP, cette tare étant largement compensée par ses talents de graphiste.
[5] Image utilisée avec l'aimable autorisation de Ayo
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.