web-dev-qa-db-fra.com

javax.faces.application.ViewExpiredException: la vue n'a pas pu être restaurée

J'ai écrit une application simple avec une sécurité gérée par conteneur. Le problème est lorsque je me connecte et ouvre une autre page sur laquelle je me déconnecte, puis je reviens à la première page et je clique sur un lien, etc. Je suppose que c'est normal (ou peut-être pas :)) car je me suis déconnecté et la session est détruite. Que dois-je faire pour rediriger l'utilisateur vers, par exemple, index.xhtml ou login.xhtml et l'empêcher de voir cette page d'erreur/ce message?

En d'autres termes, comment puis-je rediriger automatiquement d'autres pages vers la page d'index/de connexion après ma déconnexion?

C'est ici:

javax.faces.application.ViewExpiredException: viewId:/index.xhtml - View /index.xhtml could not be restored.
    at com.Sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.Java:212)
    at com.Sun.faces.lifecycle.Phase.doPhase(Phase.Java:101)
    at com.Sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.Java:110)
    at com.Sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.Java:118)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.Java:312)
    at org.Apache.catalina.core.StandardWrapper.service(StandardWrapper.Java:1523)
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:343)
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:215)
    at filter.HttpHttpsFilter.doFilter(HttpHttpsFilter.Java:66)
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:256)
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:215)
    at org.Apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.Java:277)
    at org.Apache.catalina.core.StandardContextValve.invoke(StandardContextValve.Java:188)
    at org.Apache.catalina.core.StandardPipeline.invoke(StandardPipeline.Java:641)
    at com.Sun.enterprise.web.WebPipeline.invoke(WebPipeline.Java:97)
    at com.Sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.Java:85)
    at org.Apache.catalina.core.StandardHostValve.invoke(StandardHostValve.Java:185)
    at org.Apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.Java:325)
    at org.Apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.Java:226)
    at com.Sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.Java:165)
    at com.Sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.Java:791)
    at com.Sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.Java:693)
    at com.Sun.grizzly.http.ProcessorTask.process(ProcessorTask.Java:954)
    at com.Sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.Java:170)
    at com.Sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.Java:135)
    at com.Sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.Java:102)
    at com.Sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.Java:88)
    at com.Sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.Java:76)
    at com.Sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.Java:53)
    at com.Sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.Java:57)
    at com.Sun.grizzly.ContextTask.run(ContextTask.Java:69)
    at com.Sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.Java:330)
    at com.Sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.Java:309)
    at Java.lang.Thread.run(Thread.Java:619)
164
l245c4l

Introduction

La ViewExpiredException est renvoyée chaque fois que javax.faces.STATE_SAVING_METHOD est défini sur server (valeur par défaut) et que l'utilisateur final envoie une demande HTTP POST sur une vue via <h:form> avec <h:commandLink>, <h:commandButton> ou <f:ajax>, alors que l'état d'affichage associé n'est plus disponible dans la session.

L'état d'affichage est identifié en tant que valeur d'un champ d'entrée masqué javax.faces.ViewState du <h:form>. Avec la méthode de sauvegarde d'état définie sur server, elle ne contient que l'ID d'état d'affichage qui fait référence à un état d'affichage sérialisé dans la session. Ainsi, lorsque la session a expiré pour une raison quelconque (soit expiré sur le serveur ou côté client, ou le cookie de session n'est plus conservé pour une raison quelconque dans le navigateur, ou en appelant HttpSession#invalidate() dans le serveur, ou en raison d'un serveur spécifique bug avec les cookies de session comme indiqué dans WildFly ), l'état d'affichage sérialisé n'est plus disponible dans la session et l'utilisateur final obtiendra cette exception. Pour comprendre le fonctionnement de la session, voir aussi Comment fonctionnent les servlets? Instanciation, sessions, variables partagées et multithreading .

Le nombre de vues que JSF stockera dans la session est également limité. Lorsque la limite est atteinte, la vue la moins récemment utilisée est expirée. Voir aussi com.Sun.faces.numberOfViewsInSession vs com.Sun.faces.numberOfLogicalViews .

Avec la méthode de sauvegarde d'état définie sur client, le champ d'entrée masqué javax.faces.ViewState contient à la place l'état d'affichage sérialisé dans son intégralité, de sorte que l'utilisateur final ne recevra pas un ViewExpiredException à l'expiration de la session. Cela peut toutefois se produire dans un environnement de cluster ("ERREUR: le MAC n'a pas vérifié" est symptomatique) et/ou lorsqu'un délai d'expiration spécifique à l'implémentation est défini sur l'état côté client configuré et/ou lorsque le serveur recrée la clé AES lors du redémarrage. , voir aussi Obtention de ViewExpiredException dans un environnement en cluster alors que la méthode d’enregistrement d’états est définie sur client et que la session utilisateur est valide comment le résoudre.

Quelle que soit la solution, assurez-vous de ne pas utiliser enableRestoreView11Compatibility. il ne restaure pas du tout l'état de vue d'origine. Fondamentalement, il recrée la vue et toutes les vues associées, en partant de zéro, perdant ainsi toutes les données d'origine (état). Comme l'application se comportera de manière confuse ("Hé, où sont mes valeurs d'entrée .. ??"), c'est très mauvais pour l'expérience utilisateur. Utilisez plutôt des vues sans état ou <o:enableRestorableView> afin de pouvoir les gérer sur une vue spécifique plutôt que sur toutes les vues.

En ce qui concerne le pourquoi JSF doit enregistrer l'état d'affichage, allez à cette réponse: Pourquoi JSF enregistre-t-il l'état des composants de l'interface utilisateur sur le serveur?

Eviter ViewExpiredException lors de la navigation dans les pages

Afin d'éviter ViewExpiredException lorsque, par exemple, revenir en arrière après la déconnexion lorsque la sauvegarde de l'état est définie sur server, la redirection de la demande POST après la déconnexion ne suffit pas. 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 un GET. demande sur elle (par exemple par le bouton de retour).

Le champ masqué javax.faces.ViewState 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 utilisez (ab), utilisez POST (liens de commande/boutons) au lieu de GET (liens/boutons normaux) pour la navigation de page à page, et cliquez sur ce lien/bouton de commande sur la page mise en cache. , alors cela échouera avec un ViewExpiredException.

Pour déclencher une redirection après la déconnexion dans JSF 2.0, ajoutez <redirect /> au <navigation-case> en question (le cas échéant) ou ajoutez ?faces-redirect=true à la valeur outcome.

<h:commandButton value="Logout" action="logout?faces-redirect=true" />

ou

public String logout() {
    // ...
    return "index?faces-redirect=true";
}

Pour que le navigateur ne mette pas en cache les pages JSF dynamiques, créez un Filter mappé sur le nom de servlet du FacesServlet et ajoute les en-têtes de réponse nécessaires pour désactiver le cache du navigateur. Par exemple.

@WebFilter(servletNames={"Faces Servlet"}) // Must match <servlet-name> of your FacesServlet.
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)) { // Skip JSF resources (CSS/JS/Images/etc)
            res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
            res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
            res.setDateHeader("Expires", 0); // Proxies.
        }

        chain.doFilter(request, response);
    }

    // ...
}

Éviter ViewExpiredException lors de l'actualisation de la page

Afin d'éviter ViewExpiredException lors de l'actualisation de la page en cours lorsque la sauvegarde de l'état est définie sur server, vous devez non seulement vous assurer que vous effectuez une navigation de page à page exclusivement par GET (liens habituels/boutons), mais vous devez également vous assurer que vous utilisez exclusivement ajax pour soumettre les formulaires. Si vous soumettez quand même le formulaire de manière synchrone (non ajax), vous feriez mieux de rendre la vue sans état (voir la section ultérieure) ou d'envoyer une redirection après POST (voir la section précédente).

Avoir un ViewExpiredException sur l'actualisation de 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. Ainsi, cela ne se produira que si vous définissez manuellement cette limite trop basse ou si vous créez en permanence de nouvelles vues en "arrière-plan" (par exemple, par un sondage ajax mal implémenté dans la même page ou par un système mal implémenté. 404 page d'erreur sur les images brisé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 en double dans le chemin d'accès aux classes d'exécution. La procédure à suivre pour installer JSF est décrite dans notre page wiki JSF .

Gestion de ViewExpiredException

Lorsque vous voulez gérer un ViewExpiredException inévitable après une action POST sur une page arbitraire déjà ouverte dans un onglet/fenêtre du navigateur alors que vous êtes déconnecté (e) dans un autre onglet/fenêtre, 'souhaite spécifier un error-page pour cela dans web.xml qui passe à la 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 d'actualisation de méta dans la page d'erreur au cas où vous avez l'intention de réellement rediriger vers la page d'accueil ou la page 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 dans content représente le nombre de secondes avant la redirection, 0 signifie donc "redirection immédiate", vous pouvez par exemple utiliser 3 pour laissez le navigateur attendre 3 secondes avec la redirection)

Notez que la gestion des exceptions lors des requêtes ajax nécessite un ExceptionHandler spécial. Voir aussi Traitement du délai d'expiration de session et de ViewExpiredException sur la demande ajax JSF/PrimeFaces . Vous pouvez trouver un exemple en direct sur OmniFaces FullAjaxExceptionHandler page de présentation (cela concerne également les requêtes 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 p. Ex. Java.lang.Exception ou Java.lang.Throwable, sinon toutes les exceptions encadrées dans ServletException telles que ViewExpiredException se retrouveraient toujours dans la page d'erreur générale. Voir aussi exception ViewExpiredException indiquée 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 sans état

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 sauvegardé 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'attribut transient de <f:view> sur true:

<f:view transient="true">

</f:view>

De cette façon, le champ masqué javax.faces.ViewState obtiendra une valeur fixe de "stateless" dans Mojarra (n’a pas vérifié MesFaces à ce stade). Notez que cette fonctionnalité était introduite dans Mojarra 2.1.19 et 2.2.0 et n’est pas disponible dans les versions antérieures.

La conséquence est que vous ne pouvez plus utiliser les beans view scoped. Ils vont maintenant se comporter comme des haricots à la demande. L'un des inconvénients est que vous devez suivre vous-même l'état en manipulant des entrées cachées et/ou des paramètres de demande lâches. Ce sont principalement les formulaires avec des champs de saisie avec les attributs rendered, readonly ou disabled qui sont contrôlés par des événements ajax.

Notez que le <f:view> ne doit pas nécessairement être unique dans la vue et/ou résider uniquement dans le modèle principal. Il est également tout à fait légitime de redéclarer et de l'imbrouiller dans un client modèle. 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 template client:

<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 que cela s'appliquerait à la vue entière , et pas seulement au contenu imbriqué, tel que le <h:form> dans l'exemple ci-dessus.

Voir également


Sans lien avec le problème concret, l'utilisation de HTTP POST pour une navigation de page à page pure n'est pas très conviviale pour l'utilisateur/pour le référencement. Dans JSF 2.0, vous devriez vraiment préférer <h:link> ou <h:button> à ceux <h:commandXxx> pour la navigation page à page simple dans Vanilla.

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

335
BalusC

Avez-vous essayé d'ajouter des lignes ci-dessous à votre web.xml?

<context-param>
   <param-name>com.Sun.faces.enableRestoreView11Compatibility</param-name>
   <param-value>true</param-value>
</context-param>

J'ai trouvé cela très efficace lorsque j'ai rencontré ce problème.

55
Mike GH

Avant de changer web.xml, vous devez d’abord vous assurer que votre ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Surtout si vous utilisez MyFaces

5
ZuzEL

Évitez les formulaires en plusieurs parties dans Richfaces:

<h:form enctype="multipart/form-data">
    <a4j:poll id="poll" interval="10000"/>
</h:form>

Si vous utilisez Richfaces, j'ai constaté que les requêtes ajax dans les formulaires multiparties renvoient un nouvel ID d'affichage pour chaque requête.

Comment déboguer:

À chaque demande ajax, un ID de vue est renvoyé, ce qui est correct tant que l'identifiant de vue est toujours le même. Si vous obtenez un nouvel ID de vue sur chaque demande, il y a un problème et doit être résolu.

3
tak3shi

Vous pouvez utiliser vos propres extensions AjaxExceptionHandler ou primefaces personnalisées.

Mettez à jour votre fichier faces-config.xml

...
<factory>
  <exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...

Ajouter le code suivant dans votre page jsf

...
<pe:ajaxErrorHandler />
...
0
myset

Lorsque notre page est inactive pendant une durée x, la vue expire et renvoie javax.faces.application.ViewExpiredException pour éviter que cela ne se produise. Une solution consiste à créer CustomViewHandler qui étend ViewHandler et substitue la méthode restoreView à toutes les autres méthodes déléguées. Parent

import Java.io.IOException;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

public class CustomViewHandler extends ViewHandler {
    private ViewHandler parent;

    public CustomViewHandler(ViewHandler parent) {
        //System.out.println("CustomViewHandler.CustomViewHandler():Parent View Handler:"+parent.getClass());
        this.parent = parent;
    }

    @Override 
    public UIViewRoot restoreView(FacesContext facesContext, String viewId) {
    /**
     * {@link javax.faces.application.ViewExpiredException}. This happens only  when we try to logout from timed out pages.
     */
        UIViewRoot root = null;
        root = parent.restoreView(facesContext, viewId);
        if(root == null) {
            root = createView(facesContext, viewId);
        }
        return root;
    }

    @Override
    public Locale calculateLocale(FacesContext facesContext) {
        return parent.calculateLocale(facesContext);
    }

    @Override
    public String calculateRenderKitId(FacesContext facesContext) {
        String renderKitId = parent.calculateRenderKitId(facesContext);
        //System.out.println("CustomViewHandler.calculateRenderKitId():RenderKitId: "+renderKitId);
        return renderKitId;
    }

    @Override
    public UIViewRoot createView(FacesContext facesContext, String viewId) {
        return parent.createView(facesContext, viewId);
    }

    @Override
    public String getActionURL(FacesContext facesContext, String actionId) {
        return parent.getActionURL(facesContext, actionId);
    }

    @Override
    public String getResourceURL(FacesContext facesContext, String resId) {
        return parent.getResourceURL(facesContext, resId);
    }

    @Override
    public void renderView(FacesContext facesContext, UIViewRoot viewId) throws IOException, FacesException {
        parent.renderView(facesContext, viewId);
    }

    @Override
    public void writeState(FacesContext facesContext) throws IOException {
        parent.writeState(facesContext);
    }

    public ViewHandler getParent() {
        return parent;
    }

}   

Ensuite, vous devez l'ajouter à votre fichier faces-config.xml

<application>
    <view-handler>com.demo.CustomViewHandler</view-handler>
</application>

Merci pour la réponse originale sur le lien ci-dessous: http://www.gregbugaj.com/?p=164

0
user4924157

J'avais cette erreur: javax.faces.application.ViewExpiredException.Quand j'utilise différentes requêtes, j'ai trouvé celles qui avaient le même JsessionId, même après le redémarrage du serveur. Donc, cela est dû au cache du navigateur. Fermez simplement le navigateur et essayez, cela fonctionnera.

0
rahul