introduction
Le ViewExpiredException
sera lancé chaque fois que le javax.faces.STATE_SAVING_METHOD
est défini sur server
(par défaut) et que l'utilisateur final envoie une requête HTTP POST sur une vue via <h:form>
avec <h:commandLink>
, <h:commandButton>
ou <f:ajax>
, alors que l'état de vue associé n'est plus disponible dans la session.
L'état d'affichage est identifié comme la valeur d'un champ d'entrée masqué javax.faces.ViewState
du <h:form>
. Avec la méthode d'enregistrement d'état définie sur server
, il contient uniquement l'ID d'état d'affichage qui fait référence à un état d'affichage sérialisé dans la session. Ainsi, lorsque la session est expirée pour une raison quelconque (soit expiré côté serveur ou côté client, soit le cookie de session n'est plus conservé pour une raison quelconque dans le navigateur, ou en appelant le HttpSession#invalidate()
serveur, ou en raison d'un bogue spécifique au serveur avec les cookies de session comme connu dans WildFly ), l'état de la vue sérialisée n'est plus disponible dans la session et l'utilisateur final obtiendra cette exception. Pour comprendre le fonctionnement de la session, voir également Comment fonctionnent les servlets? Instanciation, sessions, variables partagées et multithreading .
Il existe également une limite au nombre de vues que JSF stockera dans la session. Lorsque la limite est atteinte, la vue la moins récemment utilisée expire. Voir aussi com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .
Avec la méthode d'enregistrement d'état définie sur client
, le javax.faces.ViewState
champ de saisie masqué contient à la place l'ensemble de l'état de la vue sérialisée, de sorte que l'utilisateur final n'obtiendra pas de ViewExpiredException
lorsque la session expire. Cela peut cependant se produire dans un environnement de cluster ("ERREUR: MAC n'a pas vérifié" est symptomatique) et / ou lorsqu'il y a un délai d'expiration spécifique à l'implémentation sur l'état côté client configuré et / ou lorsque le serveur recrée la clé AES lors du redémarrage , voir également Obtenir ViewExpiredException dans un environnement en cluster tandis que la méthode d'enregistrement d'état est définie sur client et que la session utilisateur est valide pour la résoudre.
Quelle que soit la solution, assurez-vous de ne pas l' utiliser enableRestoreView11Compatibility
. il ne restaure pas du tout l'état d'affichage d'origine. Il recrée essentiellement la vue et tous les beans étendus de vue associés à partir de zéro et perd ainsi toutes les données d'origine (état). Comme l'application se comportera de manière déroutante ("Hey, où sont mes valeurs d'entrée .. ??"), c'est très mauvais pour l'expérience utilisateur. Mieux vaut utiliser des vues sans état ou à la <o:enableRestorableView>
place pour pouvoir la gérer sur une vue spécifique uniquement plutôt que sur toutes les vues.
En ce qui concerne la raison pour laquelle JSF doit enregistrer l'état d'affichage, dirigez-vous vers cette réponse: Pourquoi JSF enregistre l'état des composants de l'interface utilisateur sur le serveur?
Éviter ViewExpiredException lors de la navigation dans les pages
Pour éviter, ViewExpiredException
par exemple, de revenir en arrière après la déconnexion lorsque la sauvegarde d'état est définie sur server
, il ne suffit pas de rediriger la requête POST après la déconnexion. Vous devez également indiquer au navigateur de ne pas mettre en cache les pages JSF dynamiques, sinon le navigateur peut les afficher à partir du cache au lieu d'en demander une nouvelle au serveur lorsque vous envoyez une requête GET dessus (par exemple par le bouton Précédent).
Le javax.faces.ViewState
champ masqué de la page mise en cache peut contenir une valeur d'ID d'état d'affichage qui n'est plus valide dans la session en cours. Si vous (ab) utilisez POST (liens / boutons de commande) au lieu de GET (liens / boutons normaux) pour la navigation de page à page, et cliquez sur un tel lien / bouton de commande sur la page en cache, alors cela sera à son tour échouer avec un ViewExpiredException
.
Pour déclencher une redirection après la déconnexion dans JSF 2.0, ajoutez <redirect />
à la <navigation-case>
question en question (le cas échéant) ou ajoutez ?faces-redirect=true
à la outcome
valeur.
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
ou
public String logout() {
return "index?faces-redirect=true";
}
Pour demander au navigateur de ne pas mettre en cache les pages JSF dynamiques, créez un Filter
qui est mappé sur le nom de servlet du FacesServlet
et ajoutez les en-têtes de réponse nécessaires pour désactiver le cache du navigateur. Par exemple
@WebFilter(servletNames={"Faces Servlet"})
public class NoCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) {
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Pragma", "no-cache");
res.setDateHeader("Expires", 0);
}
chain.doFilter(request, response);
}
}
Éviter ViewExpiredException lors de l'actualisation de la page
Afin d'éviter l' ViewExpiredException
actualisation de la page actuelle lorsque l'enregistrement de l'état est défini sur server
, vous devez non seulement vous assurer que vous effectuez une navigation de page à page exclusivement par GET (liens / boutons réguliers), mais vous devez également vous assurer que vous utilisez exclusivement ajax pour soumettre les formulaires. Si vous soumettez le formulaire de manière synchrone (non-ajax) de toute façon, alors vous feriez mieux de rendre la vue sans état (voir la section suivante), ou d'envoyer une redirection après POST (voir la section précédente).
Avoir une ViewExpiredException
actualisation sur la page est dans la configuration par défaut un cas très rare. Cela ne peut se produire que lorsque la limite du nombre de vues que JSF stockera dans la session est atteinte. Donc, cela ne se produira que lorsque vous aurez défini manuellement cette limite trop bas, ou que vous créerez continuellement de nouvelles vues en "arrière-plan" (par exemple par un sondage ajax mal implémenté dans la même page ou par un 404 page d'erreur sur les images cassées de la même page). Voir aussi com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews pour plus de détails sur cette limite. Une autre cause est le conflit entre les bibliothèques JSF dupliquées dans le chemin de classe d'exécution. La procédure correcte pour installer JSF est décrite dans notre page wiki JSF .
Gestion de ViewExpiredException
Lorsque vous souhaitez gérer un inévitable ViewExpiredException
après une action POST sur une page arbitraire qui était déjà ouverte dans un onglet / une fenêtre de navigateur alors que vous êtes déconnecté dans un autre onglet / fenêtre, vous souhaitez spécifier un error-page
pour celui dans web.xml
lequel va vers une page "Votre session a expiré". Par exemple
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
Utilisez si nécessaire un en-tête de méta-actualisation dans la page d'erreur au cas où vous auriez l'intention de rediriger vers la page d'accueil ou de connexion.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Session expired</title>
<meta http-equiv="refresh" content="0;url=#{request.contextPath}/login.xhtml" />
</head>
<body>
<h1>Session expired</h1>
<h3>You will be redirected to login page</h3>
<p><a href="#{request.contextPath}/login.xhtml">Click here if redirect didn't work or when you're impatient</a>.</p>
</body>
</html>
(le 0
in content
représente le nombre de secondes avant la redirection, 0
signifie donc "rediriger immédiatement", vous pouvez utiliser par exemple 3
pour laisser le navigateur attendre 3 secondes avec la redirection)
Notez que la gestion des exceptions lors des requêtes ajax nécessite un fichier spécial ExceptionHandler
. Voir aussi Délai d'expiration de session et gestion de ViewExpiredException sur la requête ajax JSF / PrimeFaces . Vous pouvez trouver un exemple en direct sur la page de présentation d' OmniFacesFullAjaxExceptionHandler
(cela couvre également les demandes non-ajax).
Notez également que votre page d'erreur "générale" doit être mappée sur <error-code>
de 500
au lieu d'un <exception-type>
de par exemple java.lang.Exception
ou java.lang.Throwable
, sinon toutes les exceptions encapsulées dans ServletException
telles ViewExpiredException
qu'elles finiraient toujours dans la page d'erreur générale. Voir aussi ViewExpiredException affiché dans la page d'erreur java.lang.Throwable dans web.xml .
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
Vues apatrides
Une alternative complètement différente consiste à exécuter des vues JSF en mode sans état. De cette façon, rien de l'état JSF ne sera enregistré et les vues n'expireront jamais, mais seront simplement reconstruites à partir de zéro à chaque demande. Vous pouvez activer les vues sans état en définissant l' transient
attribut de <f:view>
sur true
:
<f:view transient="true">
</f:view>
De cette façon, le javax.faces.ViewState
champ caché obtiendra une valeur fixe de "stateless"
dans Mojarra (je n'ai pas vérifié MyFaces à ce stade). Notez que cette fonctionnalité a été introduite dans Mojarra 2.1.19 et 2.2.0 et n'est pas disponible dans les anciennes versions.
La conséquence est que vous ne pouvez plus utiliser les beans à portée de vue. Ils se comporteront désormais comme des beans à portée de requête. L'un des inconvénients est que vous devez suivre l'état vous-même en manipulant des entrées cachées et / ou des paramètres de requête lâches. Principalement ces formes avec des champs d'entrée avec rendered
, readonly
ou disabled
attributs qui sont contrôlés par les événements ajax seront affectés.
Notez que le <f:view>
n'a pas nécessairement besoin d'être unique dans la vue et / ou de résider uniquement dans le modèle principal. Il est également tout à fait légitime de le redéclarer et de l'imbriquer dans un modèle de client. Il "étend" fondamentalement le parent <f:view>
alors. Par exemple, dans le modèle principal:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
et dans le client modèle:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
Vous pouvez même envelopper le <f:view>
dans un <c:if>
pour le rendre conditionnel. Notez qu'il s'appliquerait à la vue entière , pas seulement au contenu imbriqué, comme dans l' <h:form>
exemple ci-dessus.
Voir également
Sans rapport avec le problème concret, l'utilisation de HTTP POST pour une navigation pure de page à page n'est pas très conviviale pour l'utilisateur / le référencement. Dans JSF 2.0, vous devriez vraiment préférer <h:link>
ou <h:button>
sur <h:commandXxx>
ceux pour la navigation simple de page à page.
Donc au lieu de par exemple
<h:form id="menu">
<h:commandLink value="Foo" action="foo?faces-redirect=true" />
<h:commandLink value="Bar" action="bar?faces-redirect=true" />
<h:commandLink value="Baz" action="baz?faces-redirect=true" />
</h:form>
mieux faire
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
Voir également