Conseils Pratiques pour Servlet, Partie 2
Dans la seconde partie, sur trois prévues dans cette série sur les servlets, tirée du livre Java Enterprise Best Practices, vous apprendrez à utiliser les servlets comme cache local.
Créer un Cache avec les Servlets
Voici quelques astuces à prendre en compte qui vous aideront grandement lorsque vous développez avec des servlets.
Prégénérez le Contenu et Utilisez le Cache à Fond
Prégénération et cache du contenu peuvent être les clés pour offrir aux utilisateurs de votre site la plus grande satisfaction. En prégénérant et cachant correctement, les pages jaillissent plutôt que de s’afficher péniblement, et les temps de chargement sont réduits—parfois de façon considérable—au niveau du client, serveur et réseau. Dans cette section, je vous donnerai des astuces pour mieux prégénérer l’information et créer des caches sur le client, le proxy et le serveur. A la fin de cette section, vous comprendrez qu’il n’est besoin de générer l’information lors d’une requête que dans le pire des scénarios.
Il n’est pas besoin de regénérer dynamiquement des données qui ne changent pas entre deux requêtes. Pourtant cette regénération prend place chaque fois, parce que servlets et JSPs offrent un moyen aisé d’utiliser les modèles de pages dans un site en ramenant en-têtes, pieds de page et autres contenus pendant leur exécution. Cela semble un conseil étonnant dans un chapitre sur les servlets, mais dans de nombreuses situations, les servlets ne sont pas les mieux appropriées. Il est préférable de “construire” le contenu en avance et de le retourner comme un contenu statique. Assemblez les parties du contenu quand il n’est pas demandé, plutôt qu’à chaque requête.
Prenez, par exemple, le cas d’un magazine en ligne, journal ou weblog (’blog). Comment les pros font-ils pour gérer les modèles sans alourdir le serveur ? En prégénérant le contenu. Les articles ajoutés au site sont écrits et soumis dans un format standard (souvent basé sur XML) qui, une fois passé par le processus de génération, produit une mise à jour complète du site. La génération transforme l’article en HTML, crée les liens sur l’article depuis les autres pages, ajoute le contenu dans le moteur de recherche (avant la transformation HTML), et enfin prépare le site à traiter une grande charge sans nécessiter de ressources fantastiques. Vous pouvez voir tout cela fonctionner avec des utilitaires ‘blog tels que MovableType. C’est une application Perl, mais qui génère un contenu statique, donc Perl n’a même pas besoin de tourner sur le serveur de production.
Un autre exemple, considérez un site électronique de vente au détail avec des millions d’articles à son catalogue, et des milliers de visiteurs. Il est clair que le contenu doit s’appuyer sur une base de données et être mis à jour régulièrement. Pourtant comme la plupart des données seront identiques pour les visiteurs entre les mises à jour, le site peut utiliser la prégénération de manière efficace. Les pages de description des millions d’articles peuvent être prégénérées, ce qui réduira grandement la charge du serveur. Des prégénérations régulières gardent le contenu à jour.
Le problème survient lorsqu’un mélange de contenu statique et dynamique est utilisé. Par exemple, le revendeur peut avoir besoin que des servlets gèrent les aspects dynamiques de son site, tel qu’un système de commentaires des articles ou un compteur de déconnection. Dans ce cas, le commentaire d’article peut invoquer une servlet pour mettre à jour la base de données, mais la servlet n’a pas forcément besoin de mettre la page à jour immédiatement. En fait, vous pouvez constater un tel délai dans les mises à jour du site Amazon.com—un ajout de commentaire nécessite une reconstruction du site avant de pouvoir le voir apparaître. Cependant, les pages de déconnexion dans notre exemple peuvent être implémentées entièrement par des servlets. Vous devez juste vous assurer de bien coordonner la construction des pages pour que l’aspect des modèles utilisés par les servlets et ceux utilisés pour le contenu prégénéré soient identiques.
Outils de prégénération
Malheureusement, il existe peu d’outils de qualité professionnelle, à un prix raisonnable, permettant de prégénérer les données de votre site. La plupart des sociétés et administrateurs de site doivent soit acheter des systèmes sophistiqués de gestion de contenu ou développer des outils maison pour satisfaire leurs besoins. C’est peut-être pour cela que les développeurs utilisent la prégénération que lorsque la charge du site le nécessite.
Pour ceux à la recherche d’un outil, le projet Jakarta du groupe Apache gère son propre contenu par le module Anakia. Développé autour de Velocity d’Apache, Anakia transforme le contenu XML par un feuille de style XSL pour produire du code HTML statique. Ants d’Apache, le fameux système de construction de projet Java, gère la construction du site. D’autres ont eu du succès avec les modèles de Dreamweaver de Macromedia. Dreamweaver offre l’avantage d’afficher les fichiers JSP, WebMacro, Velocity, et Tea comme de simples fichiers modèles dont le contenu HTML est mis à jour automatiquement quand les modèles sont modifiés, proposant un pont entre le statique et le dynamique.
Il y a un besoin important pour un bon outil commun. Si vous pensez que vous avez le bon outil, veuillez le partager ou en parler. Il se peut que cet outil existe quelque part et nous n’en avons pas encore entendu parlé.
Cache sur le client
Prégénération et cache vont bien ensemble parce qu’un cache ne fait rien d’autre que de conserver ce qui a été précédemment généré. Les navigateurs web (c’est à dire les clients) ont tous un cache, et il ne reste plus qu’au développeur de servlet à l’exploiter. L’en-tête HTTP Last-Modified est la clé pour un cache efficace. Faisant partie de la réponse, cet en-tête indique au navigateur la date de dernière modification du contenu. Ceci est très pratique parce que si le navigateur redemande le même contenu, il attache un en-tête If-Modified-Since initialisé avec le temps de Last-Modified, qui indique au serveur qu’il doit renvoyer une réponse complète seulement si le contenu à été modifié depuis cette date. Si le contenu est resté inchangé, le serveur peut retourner une réponse avec un code 304, et le client peut charger le contenu depuis son cache, évitant complètement les méthodes doGet() et doPost(), ménageant ainsi les ressources serveur et la bande passante.
Une servlet peut accéder à Last-Modified en implémentant la méthode getLastModified() elle-même. Cette méthode retourne la date à laquelle le contenu à été dernièrement modifié sous forme de long, comme présenté dans l’exemple 3-6. C’est tout ce que la servlet a à faire. Le serveur se charge d’envoyer l’en-tête HTTP et d’intercepter les requêtes If-Modified-Since.
Exemple 3-6: La méthode getLastModified( )
public long getLastModified(HttpServletRequest req) {
return dataModified.getTime( ) / 1000 * 1000;
}
La méthode getLastModified() est facile d’implémentation et devrait être implémentée pour tout contenu dont la durée de vie est supérieure à une minute. Pour plus d’informations sur getLastModified(), voyez mon livre, Java Servlet Programming, Second Edition (O’Reilly).
Cache sur le proxy
Bien que l’utilisation de getLastModified() pour utiliser le cache du client est une bonne idée, il y a d’autres caches plus importants à considérer. Souvent, spécialement au sein des companies ou des fournisseurs d’accès importants, les navigateurs utilisent un proxy pour se connecter à internet. Ces proxys implémentent généralement un cache partagé des données qu’ils récupèrent, si bien que si un autre utilisateur (ou le même) le redemande, le proxy peut retourner la réponse sans avoir à accéder au serveur web original. Les proxys réduisent le temps d’attente, et améliorent l’utilisation de la bande passante. Le contenu peut venir du monde entier, mais il est servi comme s’il était sur le réseau local—parce que c’est le cas.
L’en-tête Last-Modified s’applique aux caches web parce qu’elle force la mise en cache sur le client, mais les caches web peuvent être encore améliorés si une servlet indique au cache quand le contenu est sur le point de changer, permettant au cache, pendant un laps de temps, de retourner son contenu sans même avoir à se connecter au serveur. La manière la plus simple de faire cela est d’initaliser l’en-tête Expires, indiquant la date à laquelle le contenu sera considéré obsolète. Par exemple :
// Ce contenu va expirer dans 24 heures.
response.setDateHeader("Expires",
System.currentTimeMillis( ) + 24*60*60*1000);
Si vous suivez mon conseil précédent et prégénérez tous les jours quelques sections de votre site, vous pouvez initialiser Expires sur ces pages en conséquence, et voir les caches des proxys distribués décharger votre serveur. Certains clients peuvent également utiliser Expires pour éviter de ramener les données qu’ils possèdent déjà.
Une servlet peut également initialiser d’autres en-têtes. L’en-tête Cache-Control offre des paramètres avancés pour interagir avec le cache. Par exemple, initialiser la valeur de l’en-tête à only-if-cached ne demande le contenu que s’il est dans le cache. Pour de plus amples informations sur Cache-Control, voyez http://www.servlets.com/rfcs/rfc2616-sec14.html#sec14.9. Un vaste aperçu des stratégies de cache est également disponible sur http://www.mnot.net/cache_docs. Ce site propose également des astuces pour le décompte des pages accédées même placées dans le cache.
Cache sur le serveur
Utiliser le cache au niveau des client et proxy aide si la requête provient de la même personne ou organisation, mais qu’arrive-t-il aux autres requêtes provenant d’autres navigateurs. C’est pourquoi le dernier niveau de cache doit être sur le serveur. Tout contenu qui prend du temps ou de la ressource pour être généré mais ne change généralement pas entre deux requêtes est un bon candidat pour une mise en cache sur le serveur. De plus, le cache au niveau serveur marche aussi bien pour des pages entières (à l’opposé d’autres technologies de cache) que pour des parties de pages.
Prenez, par exemple, une mise à jour de texte RSS. Si cela ne vous est pas familier, RSS signifie Rich Site Summary (Sommaire de Site Complexe) et est constitué d’un fichier au format basé sur XML par lequel les éditeurs annoncent de nouveaux articles. Les sites associés peuvent récupérer les fichiers RSS et faire afficher les liens entre sites. Servlets.com, par exemple, pointe sur des articles relatifs aux servlets, en utilisant RSS avec O’Reilly’s Meerkat comme serveur RSS.
Supposez que vous vouliez afficher les élément RSS d’un site associé, mis à jour toutes les 30 minutes. Cette donnée doit absolument être mise dans un cache au niveau du serveur. Non seulement il serait trop lent d’extraire les nouveaux éléments RSS pour chaque requête, mais c’est de plus une mauvaise solution. Le cache peut être implémenté en utilisant un timer interne ou, plus facilement, un comparateur de date. Le code pour extraire les nouvelles informations peut vérifier à chaque accès s’il est temps de ramener les données et rafraîchir l’affichage. Comme les données sont peu volumineuses, le contenu formatté peut être conservé en mémoire. L’exemple 3-7 présente une partie du code qui pourrait gérer le cache. En plus de ce cache on pourrait avoir un autre cache pour conserver la String (or bytes) à afficher.
Exemple 3-7: Mise en cache des informations RSS
public Story[ ] getStories(String url) {
Story[ ] stories = (Story[ ]) storyCache.get(url);
Long lastUpdate = (Long) timeCache.get(url);
long halfHourAgo = System.currentTimeMillis( ) - 30*60*1000;
if (stories = = null || stories.length = = 0 ||
lastUpdate = = null || (lastUpdate.longValue( ) < halfHourAgo)) {
refetch( );
}
return stories;
}
En second exemple, prenez un diagramme de valeurs boursières comme il s’en trouve sur tout site financier. Ce cas représente un autre type de problème, parce que le site peut supporter des milliers de valeurs, chacune proposant des diagrammes de différente taille avec différentes périodes et parfois même des comparaisons graphiques d’évolution entre valeurs. Pour cette application, la mise en cache sera absolument nécessaire parce que la génération des diagrammes prend un temps et des ressources non négligeables, et que la génération dynamique de chaque diagramme ferait exploser les spécifications du serveur.
Une bonne solution est à facettes multiples. Certains diagrammes peuvent être prégénérés (comme nous en avons discuté plus haut). Ces diagrammes seraient servis en tant que fichiers. Cette technique fonctionne pour les diagrammes les plus fréquemment accédés qui ne changent pas plus d’une fois par jour. D’autres diagrammes, peut-être ceux qui sont très souvent accédés mais qui changent fréquemment, tels que les diagrammes d’activité journalière pour les actions populaires, bénéficieraient à être mis en cache en mémoire et servis directement. Ils pourraient être sauvegardés en utilisant une SoftReference. Les références soft libèrent la mémoire de la Machine Virtuelle Java (JVM) si nécessaire. Enfin, d’autres diagrammes, les moins populaires ou les moins à même de changer, pourraient bénéficier d’être mis en cache par les servlets sur le système de fichier, comme un fichier temporaire à accès semi-aléatoire dont les données peuvent être extraites par une servlet au lieu d’être générées par la servlet. La méthode File.createTempFile() peut aider à gérer de tels fichiers.
De nombreuses solutions potentielles existent, et tout ceci ne doit pas être pris pour parole d’Evangile. Le point important est que les caches mémoire, caches en fichiers temporaires et fichiers statiques prégénérés sont de bons éléments pour la conception d’une gestion de cache sur un serveur.
Au-delà du cache serveur, il est important de rappeler les caches client et proxy. Les pages de diagrammes devraient implémenter getLastModified() et initialiser les en-têtes Expires et/ou Cache-Control. Cela réduira la charge du serveur et améliorera la réactivité d’autant plus.
…Ou n’utilisez pas de cache du tout
Même si mettre en cache semble plein de bon sens la plupart du temps et devrait être mis en place dans la mesure du possible, certains types de contenus ne sont pas adaptés à l’utilisation de cache et doivent toujours être à jour. Prenez, par exemple, un indicateur de situation courante ou une page “Veuillez patienter…” qui utilise la balise Refresh pour accéder régulièrement au serveur pendant l’attente. Mais à cause du nombre important d’endroits où le contenu peut être mis en cache—au niveau du client, proxy et serveur—et à cause de fameux bugs de navigateurs internet, il peut être difficile de désactiver le cache à tous les niveaux.
Après avoir passé des heures à tenter de désactiver la mise en cache, les programmeurs se sentent comme des magiciens cherchant en vain le bon sortilège. Eh bien, Harry Potter, propose la bonne formule dans l’exemple 3-8, issue d’expérience personnelle et de recommendations des magiciens du Net.
Exemple 3-8: La formule magique pour désactiver la mise en cache
// Réglé pour expirer dans un passé lointain.
res.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
// Réglage des en-têtes no-cache HTTP/1.1 standard.
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Réglage des en-têtes no-cache HTTP/1.1 étendues de IE (utilise addHeader).
res.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Réglage de l'en-têtes no-cache HTTP/1.0 standard.
res.setHeader("Pragma", "no-cache");
L’en-tête Expires indique que les pages ont expiré il y a longtemps, les rendants impropres à la mise en cache. Le premier en-tête Cache-Control met en place trois directives qui, chacune, désactivent la mise en cache. Une indique de ne pas mettre ce contenu dans le cache, une autre de ne pas utiliser ce contenu pour servir une autre requête, et la dernière de toujours revalider le contenu sur une requête s’il est expiré (ce qui sera toujours le cas ici). Une directive devrait être suffisante, mais en magie et sur le Web, il est toujours préférable de prendre ses précautions.
Le second en-tête Cache-Control met en place deux “extensions” relatives au cache, supportées par Microsoft Internet Explorer. Sans aller dans les détails sur les directives non standard, il est suffisant de dire qu’initialiser pre-check et post-check à 0 indique que le contenu devrait toujours être récupéré. Comme c’est un ajout d’une autre valeur à l’en-tête Cache-Control, nous utilisons addHeader(), introduit par l’API servlet version 2.2. Pour les containeurs de servlet supportant les versions antérieures, vous pouvez combiner les deux instructions en une seule ligne.
Le dernier en-tête, Pragma, est défini par HTTP/1.0 et est supporté par quelques technologies de cache qui ne reconnaissent pas Cache-Control. Mettez ces en-têtes ensemble, et vous avez un mélange surpuissant pour désactiver la mise en cache. Certains programmeurs ajoutent également une méthode getLastModified() qui retourne une date dans le passé.
Dans le prochain articles vous apprendrez encore d’autres astuces sur les Servlets.

Textes originaux en anglais sur O’Reilly : Servlet Best Practices, Part 2 par Jason Hunter
Chargement
Commentaires récents