Accueil > WebObjects > Applications Web : Gestion des Retours Arrière et du Cache

Applications Web : Gestion des Retours Arrière et du Cache

Par Apple Computer, Inc. © 2002 (Dernière mise à jour 3 Janvier 2002),

Traduit par Thierry, 11/03/2003.

Le retour arrière, la mise en cache des pages côté client et celle des composants web sont trois problèmes étroitement liés qui provoquent des maux de tête chez les développeurs d’applications web. Heureusement, WebObjects apporte de nombreux mécanismes pour vous aider à traiter le problème collectif de la gestion de l’état d’une page.

Les applications web dynamiques tiennent, entre autres choses, à la persistence de l’état côté serveur et à la gestion de l’état. HTTP, le protocole du web, n’a pas la faculté inhérente de gèrer cet état. Cependant, le fait de stocker l’état dans un serveur d’applications rend possible la gestion de la persistence dans les applications web. Dans WebObjects, l’objet Session supporte l’état mais il n’est pas seul responsable de sa gestion. L’objet Session trace les sessions, marque les objets WOComponent et WOElement avec des identifiants spéciaux, et utilise d’autres mécanismes pour supporter et gérer l’état. Les objets WOComponent gèrent l’état des variables d’instance et des éléments dynamiques internes.

Au côté de ces mécanismes, la mise en cache joue un rôle important dans la gestion de l’état des composants visuels. La gestion du cache ou “caching” permet à un utilisateur de voir une page déjà vue (même une page générée dynamiquement) sans que l’application ait besoin de regénérer la page. Le caching joue aussi un rôle crucial dans la constitution d’une bonne expérience utilisateur des applications Web. Le caching permet aux utilisateurs de revenir en arrière en utilisant le bouton Page Précédente de leur navigateur Web, qui permet souvent de charger la page à partir du cache côté client au lieu de requêrir de nouveau auprès du serveur une page déjà vue. Cependant, à cause des diverses implémentations du protocole HTTP dans les navigateurs Web, le retour arrière n’est pas toujours géré de la même manière et nécessite une attention considérable lors du développement d’applications web.

En plus du caching des pages côté client, WebObjects stocke aussi les composants dans un cache côté serveur. Utilisée correctement, c’est une fonction appréciable pouvant améliorer les performances et l’expérience de l’utilisateur. Mais vous devez être conscient de la relation qu’il y a entre le caching des composants côté serveur et le caching des pages côté client, et à quel point des inconsistances dans la gestion du retour arrière peuvent affecter le résultat lorsque l’une ou l’autre des fonctions de caching est active.

Cache des pages côté client

Un composant web est un agrégat d’éléments et de sous-composants WebObjects. Lorsqu’un navigateur web met en cache une page web issue d’une application WebObjects, il met en cache le code HTML de la page générée (code qui ne comprend pas des entités de programmation du composant Web tels que des variables d’instance). A l’opposé, la mise en cache des composants côté serveur affecte la définition du composant et son état.

Le caching des pages côté client est une fonction implémentée dans les navigateurs web dans le but d’améliorer les performances et l’expérience utilisateur. Bien que les applications WebObjects publient avant tout des pages web dynamiques, beaucoup de sites web servent des pages statiques : elles ne changent pas aussi rapidement que des sites à contenus dynamiques.

Par exemple, considérez un site web qui publie des news et des articles. Bien que la page d’accueil change probablement plusieurs fois par jour, elle ne changera sûrement pas durant les quelques minutes qu’un lecteur passera à consulter les titres et à lire quelques articles.

Avec un caching des pages côté client actif, la page d’accueil du site de news est mise en cache sur l’ordinateur de l’utilisateur lors de sa première visite. Cette première page pourrait être importante avec des images, des bannières de pub et du texte. L’utilisateur pourrait sélectionner un article, en lire une partie et accéder à d’autres articles via des liens insérés dans le premier article. Puis, ayant visité cinq ou six pages du site, il pourrait revenir à la page d’accueil. Puisque le contenu de cette page n’est pas supposé avoir changé durant les minutes passées à feuilleter les cinq ou six pages, elle peut être rechargée à partir du cache local. Ainsi, le navigateur web—au lieu de requérir et de télécharger une fois encore la page principale à partir du serveur web—la récupérerait à partir du cache local, évitant un passage par le réseau vers le serveur web. Dans ce cas, le caching est au service d’une fonction sensible et conviviale.

Maintenant, considérez le cas d’une boutique en ligne : Un utilisateur choisit des articles à acheter et les ajoute à son caddie. En générale, il n’est pas approprié de montrer à l’utilisateur une page mise en cache représentant le caddie puisqu’elle n’aura pas les informations les plus à jour. Si le caching des pages côté client est actif, cependant, il se peut que cela arrive.

WebObjects offre plusieurs mécanismes pour traiter des problèmes de retour arrière et de caching côté client. Le premier que vous devriez utiliser est un indicateur apposé sur l’objet application que vous réglez en utilisant la méthode setPageRefreshOnBacktrackEnabled de la classe WOApplication (com.webobjects.appserver). Lorsque pageRefreshOnBacktrackEnabled vaut true, quelques headers HTTP headers sont ajoutés à chaque réponse générée par l’application WebObjects afin de désactiver le caching des pages côté client. Le Tableau 6-1 montre ces headers et leurs valeurs.

Table 6-1 Headers HTTP de la réponse qui désactivent le caching des pages côté client

Header Valeur
date Date et heure à laquelle la page a été générée.
expires Date et heure à laquelle la page expire. (Même valeur que date.)
pragma no-cache
cache-control private, no-cache, no-store, must-revalidate, max-age = 0

Reportez-vous à la section 14.9 des spécifications HTTP 1.1 (RFC 2616) pour en savoir plus sur ces headers.

La propriété pageRefreshOnBacktrackEnabled affecte toutes les réponses générées par une application. Si vous souhaitez restreindre le comportement d’une réponse spécifique, invoquez la méthode disableClientCaching de l’objet WOResponse (com.webobjects.appserver). WOResponse comprend aussi les méthodes setHeader et setHeaders, qui vous permettent de régler explicitement les headers HTTP d’une réponse particulière.

Quand un navigateur reçoit une page avec les headers illustrés dans le Tableau 6-1, il ne devrait pas ajouter cette page à son cache local et il devrait invalider la page aussitôt qu’elle a été affichée. En d’autres mots, si l’utilisateur revient en arrière sur des pages déjà vues, le navigateur devrait requérir la page auprès de l’application serveur. Cependant, tous les navigateurs ne respectent pas ce protocole, comme illustré dans “Gestion du retour arrière dans les navigateurs”. Les toutes premières fois qu’un utilisateur revient sur une page déjà vues, la plupart des navigateurs ignorent les en-têtes HTTP et présente la page stockée dans le cache.

Lorsqu’un navigateur doit rafraîchir une page expirée, il envoie une requête à l’application serveur qui accède au cache côté serveur pour reconstruire la page (se reporter à “Cache des pages côté serveur” pour plus d’informations sur le caching côté serveur). Le paragraphe “Traitement de la requête” explique en détail les phases de la boucle requête-réponse. Les phases principales sont synchronisation, action et réponse. Lors du traitement d’une requête de rafraîchissement, une application ne passe pas par les phases de synchronisation et d’action ; elle ne passe que par la phase de réponse.

Donc, comment fait une application pour savoir qu’elle ne doit passer que par la phase de réponse (et juste retourner la page stockée dans le cache serveur au lieu de la regénérer) ? WebObjects assigne un ID de contexte à chaque réponse. L’ID de contexte est incrémenté de 1 à chaque fois qu’un navigateur requiert une page spécifique auprès de l’application durant une session. Il identifie une instance spécifique du WOComponent correspondant. (La Figure 6-1 illustre les éléments d’une URL WebObjects). Une application assigne spécifiquement un ID de contexte au composant le plus extrême d’un WOComponent chaque fois que ce composant fait partie de la réponse. Ainsi, si le même composant est généré dynamiquement plusieurs fois, à chaque instance de la page (chaque réponse) est assigné un ID de contexte unique.

Figure 6-1 Structure d’une URL de composant action
[image: ../art/incomingurl.gif]

Cache de définition des composants côté serveur

Lorsqu’un composant web est accédé pour la première fois, sa définition est placée dans le cache côté serveur. Les requêtes suivantes portant sur le même composant utilisent la définition stockée dans le cache. L’utilisation du cache d’une composant web améliore les performances parce que l’application ne recherche la définition d’un composant qu’une seule fois durant la vie de l’application. Vous pouvez contrôler le caching des composants web au niveau de l’application et au niveau du composant. Vous pouvez mettre en place une politique propre à l’application (active ou inactive) pour tous les composants, mais vous pouvez aussi déroger à cette politique pour des composants spécifiques. Pour mettre en place une politique de caching pour une application ou un composant web, vous utilisez la méthode setCachingEnabled de WOApplication ou de WOComponent, respectivement. L’envoi de true comme argument active le caching de définition des composants alors que l’envoi de false la désactivate.

Cache des Pages Côté Serveur

En plus du caching des défintiions de composants, les applications WebObjects peuvent aussi mettre en cache les réponses envoyées à un client. Lorsqu’une page qui a déjà été générée est requise auprès de l’application serveur, WebObjects compare l’ID de contexte de la page générée avec ceux des pages de son cache. S’il tombe sur une correspondance, il effectue la phase de réponse de la boucle requête-réponse. Cela renvoie une réponse dotée d’un nouvel ID de contexte et un contenu mis à jour par l’invocation de la phase de réponse de la boucle requête-réponse (les liens dynamiques sont de nouveau résolus dans la phase de réponse).

Par défaut, l’application serveur WebObjects maintient un cache de pages pour chaque session. Chaque page qu’un utilisateur accède est ajoutée au cache de pages de la session. Lorsqu’un utilisateur revient en arrière, accède une URL ou sélectionne le signet d’une page qui a été mise en cache mais qui a expiré dans le cache local, le navigateur web requiert une version mise à jour de cette page auprès de l’application serveur. Le cache des pages côté serveur préserve les ressources de part le fait qu’il met à disposition le résultat de pages précédemment générées. Lorsque la page sur laquelle l’utilisateur revient n’est plus en cache, WebObjects retourne une page d’erreur.

Si vous désactivez le cache des pages côté serveur (en passant 0 à la méthode setPageCacheSize de WOApplication), l’application suppose que vous avez l’intention de fournir une persistence personnalisée d’état de composants plutôt que de vous reposer sur le support inhérent à WebObjects. La désactivation du cache des composants signifie que les nouveaux objets WOComponent sont instanciés (c’est à dire, chaque requête de composant crée une nouvelle instance de ce composant) avec chaque cycle de la boucle requête-réponse, même pour les requêtes de composants action qui retournent la page invoquée. Cela veut dire que toute valeur de variables d’instance (mises à part les valeurs par défaut) est annulée avec chaque cycle consécutif de la boucle requête-réponse. Dans des applications importantes, cette redondance et ce temps machine pourrait entraver les performances.

WebObjects apporte aussi un cache permanent de pages qui est utile pour stocker des sous-composants tels que des barres de navigation ou des en-têtes de pages, ou lors de l’usage d’ensembles de frames. Vous devez explicitement y ajouter des composants en utilisant la méthode savePageInPermanentCache de WOSession (com.webobjects.appserver). Voir les renseignements relatifs à l’API pour plus de détails.

Gestion du Retour Arrière dans les Navigateurs Web

Pour mieux comprendre les concepts de retour arrière, de caching des pages côté client et de caching de définitions de composants, effectuez les taches décrites dans les paragraphes suivants.

Voir les En-têtes HTML

Ouvrez le projet TimeDisplay décrit dans “Développer du Contenu Dynamique”.

Dans Main.java, ajoutez la méthode appelée outgoingHeaders:

public String outgoingHeaders() {
    return context().response().headers().toString();
}

Cela récupère les en-têtes qui sont rattachées à chaque WOResponse. Pour voir les en-têtes, surpasser la méthode sleep dans la classe Main de façon à ce qu’elle affiche les en-têtes sur la console :

public void sleep() {
    System.out.println("<Main.sleep> headers=" + outgoingHeaders());
}

Construisez et lancez l’application. Vous devriez voir un affichage semblable à ceci sur la console :

Welcome to TimeDisplay!
[2003-01-08 17:53:56 PST] <main> Opening application's URL in browser:
http://17.203.33.19:8888/cgi-bin/WebObjects/TimeDisplay.woa
[2003-01-08 17:53:56 PST] <main> Waiting for requests...
<Main.sleep>
headers={cache-control = ("private",  "no-cache", "no-store", "must- revalidate", "max-age=0");
expires = ("Thu,  09-Jan-2003 01:53:54 GMT");
date =  ("Thu, 09-Jan-2003 01:53:54 GMT");
pragma = ("no-cache");
content-type = ("text/ html"); }

L’en-tête expires est réglée sur la date et l’heure à laquelle le composant est généré, ainsi lorsque le navigateur reçoit la page web, elle est déjà expirée dans le cache du navigateur. Ces en-têtes (à part content-type) sont ajoutées à la réponse quand la méthode isPageRefreshOnBacktrackEnabled de WOApplication renvoie true, valeur retournée par défaut.

Dans Application.java, régler la propriété pageRefreshOnBacktrackEnabled sur false dans le constructeur :

public Application() {
    super();
    System.out.println("Welcome to " + this.name() + "!");
    setPageRefreshOnBacktrackEnabled(false);
}

Construisez et lancez l’application. Vous devriez voir un affichage semblable à ceci sur la console :

Welcome to TimeDisplay!
[2003-01-08 17:57:15 PST] <main> Opening application's URL in browser:
http://17.203.33.19:8888/cgi-bin/WebObjects/TimeDisplay.woa
[2003-01-08 17:57:15 PST] <main> Waiting for requests...
<Main.sleep> headers={content-type = ("text/html"); }

Remarquez que les en-têtes désactivant le caching côté client ne sont pas générées dans la réponse.

Retour Arrière sur des Pages Web Standard

Donc, comment la propriété pageRefreshOnBacktrackEnabled de WOApplication affècte le retour arrière de l’utilisateur ? Vous devez ajouter encore un peu de code pour tracer ce que WebObjects effectue en coulisse. Modifier le constructeur Main de la façon suivante :

public Main(WOContext context) {
    super(context);
    System.out.println("<Main> context ID="+ context().contextID());
}

Chaque fois que l’instance de Main est créée, ce code affiche l’ID de contexte de la WOResponse associée à la nouvelle instance. Cela vous permet de voir à quel moment des actions utilisateur telles que cliquer sur l’hyperlien “Refresh” de la page web ou cliquer sur le bouton “Page Précédente” du navigateur produisent une nouvelle instance du composant Main. Bien que ces informations soient utiles, vous pourriez aussi souhaiter de savoir quand une action utilisateur cause l’envoi par l’application d’une nouvelle page de réponse vers le navigateur. Vous pouvez tracer ceci en ajoutant le code suivant à la méthode refreshTime :

public WOComponent refreshTime() {
    System.out.println("<Main.refresh> contextID=" + context().contextID());
    loadCount++;
    return null;
}

Maintenant, retirer les méthodes sleep et outgoingHeaders, construisez et lancez l’application.

Cliquez sur “Refresh Time” trois fois. Cela un ID de contexte incrémental de l’instance de Main au travers de laquelle vous naviguez. Lorsque vous cliquez sur “Refresh Time”, l’application invoque la méthode refreshTime, ce qui provoque l’affichage de l’ID de contexte de la réponse sur la console :

Welcome to TimeDisplay!
[2003-01-08 18:56:18 PST] <main> Opening application's URL in browser:
http://17.203.33.19:8888/cgi-bin/WebObjects/TimeDisplay.woa
[2003-01-08 18:56:18 PST] <main> Waiting for requests...
<Main> context ID=0
<Main.refreshTime> context ID: 1
<Main.refreshTime> context ID: 2
<Main.refreshTime> context ID: 3

Maintenant, cliquez sur le bouton “Page Précédente” de votre navigateur trois fois. Notez que rien n’est affiché sur la console. Cela vient du fait que lorsque pageRefreshOnBacktrackEnabled est réglé sur false, le retour arrière ne résulte pas un une requête envoyée à l’application : la page est simplement présentée en utilisant sa copie située dans le cache du navigateur. De la même manière, le fait de choisir le signet d’une page mise en cache dans le navigateur ne résulte pas en une requête envoyée à l’application.

Rafraîchir les Pages lors de Retour Arrière

Quand pageRefreshOnBacktrackEnabled est réglé sur true, le retour arrière devrait provoquer l’envoi d’une requête vers l’application (vous devriez voir un ID de contexte avec une nouvelle valeur) lorsque l’utilisateur revient en arrière, bien que le comportement réel diffère d’un navigateur à l’autre.

Dans Mac OS X, les navigateurs web qui utilisent le moteur de rendu HTML Gecko (tels que Chimera et Mozilla), respectent plus les spécifications HTTP. Le fait de cliquer sur le bouton “Page Précédente” provoque l’envoi d’une demande de version à jour d’une page expirée. D’autres navigateurs, tels que Internet Explorer et OmniWeb, se comportent différemment : les premiers clics (deux ou trois en fonction du navigateur) sur le bouton “Page Précédente” provoque le rechargement de la page à partir du cache. Les clics consécutifs provoquent l’envoi par le navigateur d’une requête vers l’application.

Remarquez que lorsque le navigateur requiert une version à jour de la page web auprès de l’application, le compteur de chargement de la page ne diminue pas mais l’heure est mise à jour.

Vous devez tester votre application sur plusieurs configurations pour être sûr qu’elle apporte une bonne expérience à l’utilisateur.

Désactivation du Caching Côté Serveur

Une application WebObjects peut conserver que la réponse de la page précédemment générée quand le caching des pages côté serveur est actif, ce qui est le comportement par défaut. Quand cette fonction est inactive, l’instruction println dans le constructeur de la classe Main (de l’application TimeDisplay évoquée précédemment) est invoquée chaque fois que vous cliquez sur le lien “Refresh Time”. Cela indique à l’application d’instancier un objet Main chaque fois que la méthode refreshTime de Main est invoquée, au lieu de renvoyer l’objet actuel de Main.

Modifiez le constructeur de Main en ajoutant un appel à setPageCacheSize:

public Application() {
    super();
    System.out.println("Welcome to " + this.name() + "!");
    setPageRefreshOnBacktrackEnabled(true);
    setPageCacheSize(0);
}

Construisez et lancez l’application. Après avoir cliqué sur “Refresh Time” trois fois, vous devriez voir un affichage semblable à ceci sur la console :

Welcome to TimeDisplay!
[2003-01-08 20:31:58 PST] <main> Opening application's URL in browser:
http://17.203.33.19:8888/cgi-bin/WebObjects/TimeDisplay.woa
[2003-01-08 20:31:57 PST] <main> Waiting for requests...
<Main> context ID=0
<Main> context ID=1
<Main.refreshTime> context ID: 1
<Main> context ID=2
<Main.refreshTime> context ID: 2
<Main> context ID=3
<Main.refreshTime> context ID: 3

Remarquez que le constructeur de Main est invoqué chaque fois que vous cliquez sur “Refresh Time”, avant que la méthode refreshTime ne soit exécutée. Une instance de Main est créée lors de chaque cycle de la boucle requête-réponse. Remarquez aussi que le compteur de vue de la page n’augmente pas. La conséquence principale de la désactivation du caching côté serveur est que les valeurs des variables des composants sont perdues après chaque génération de réponse.

Réglage de la Taille du Cache Côté Serveur

Au lieu de désactiver complètement le caching côté serveur, vous pouvez utiliser la méthode setPageCacheSize de WOApplication pour définir le nombre d’instances d’un composant qu’une application doit garder dans son cache. Par exemple, si vous souhaitez maintenir l’état entre les cycles de la boucle requête-réponse (c’est à dire, s’assurer que l’état est transféré entre les actions utilisateur), réglez le pageCacheSize sur 1.

Modifiez le constructeur de l’Application en ajoutant un appel à setPageCacheSize, de façon à régler la propriété pageCacheSize sur 10.

public Application() {
    super();
    System.out.println("Welcome to " + this.name() + "!");
    setPageRefreshOnBacktrackEnabled(true);
    setPageCacheSize(10);
}

La Figure 6-2 illustre la page qu’une application envoie à un navigateur web lorsque l’utilisateur revient trop loin en arrière (la page n’est plus dans le cache).

Figure 6-2 Page web d’erreur sur retour arrière
[image: ../art/webbacktrackfar.gif]

Vous pouvez personnaliser la page d’erreur que les utilisateurs recoivent en implémentant la méthode handlePageRestorationErrorInContext dans la classe Application :

public WOResponse handlePageRestorationErrorInContext(WOContext aContext) {
    WOComponent nextPage;
    nextPage = (Error)pageWithName("Error", aContext);
    return nextPage.generateResponse();
}

Dans ce bout de code, une page est instanciée à partir d’un composant web nommé Error, que vous devez construire. Le contenu du composant est complètement de votre ressort mais vous devriez inclure le nom de l’application, celui de votre société et un message amical indiquant à l’utilisateur que quelque chose s’est mal passé et suggérant des manières de revenir à des opérations normales.

Textes originaux en anglais sur developer.apple.com : WebObjects Web Application - Backtracking and Cache Management

Thierry WebObjects ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire