jsfomnifaces

When using org.omnifaces.cdi.ViewScoped my javax.servlet.Filter receives both a GET and a POST on a single page view


My servletfilter for logging page views is receiving a GET and a POST with a single page request. I tracked it back to using Omnifaces ViewScope on page backing bean.

@Named
@org.omnifaces.cdi.ViewScoped

I happen to notice double page views in my log file with exact timestamps. Debugging with the below simplified version, on a single page request, the doFilter is executed twice, first time is a GET and the URL is the URL I am browsing to. Then doFilter is executed again and it is a POST and the URL is the page I came from. If I page refresh I'll see a GET then a POST to the same page. If I use javax.faces.view.ViewScoped, only GET requests come in.

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    System.out.println(new java.util.Date() + " " + ((HttpServletRequest) request).getMethod() + " " + httpRequest.getServletPath());
    chain.doFilter(request, response);
}

Example, if I am viewing http://localhost:8080/myApp/page1.xhtml and I change url to page2.xhtml

the filter will write out

Wed Aug 26 12:17:04 EDT 2020 GET /page2.xhtml
Wed Aug 26 12:17:04 EDT 2020 POST /page1.xhtml

Perhaps by design?. But I only want to log the page that the user is browsing to, not the one they came from. Is it as simple as?:

    if(((HttpServletRequest) request).getMethod().equalsIgnoreCase("GET")){
      //Write to actual log file
    }

or am I using omnifaces viewscope wrong?


Solution

  • This is indeed by design. The POST on the previous page basically sends a signal that the page has been unloaded and thus the logic behind the @ViewScoped knows that it has to immediately destroy the JSF view state and the physical bean.

    See also the documentation:

    ... this CDI view scope annotation will guarantee that the @PreDestroy annotated method is also invoked on browser unload. This trick is done by navigator.sendBeacon. For browsers not supporting navigator.sendBeacon, it will fallback to a synchronous XHR request.

    When you want to detect them in your filters, then you can use ViewScopeManager#isUnloadRequest().

    if (!ViewScopeManager.isUnloadRequest(request)) {
        // Write to actual log file.
    }
    
    chain.doFilter(request, response); // Ensure that this just continues!
    

    This is also documented under the section "Detecting unload requests".

    When the unload request has hit your servlet filter or authentication mechanism or whatever global listener/observer, and you would like to be able to detect them, so that you can exclude them from the logic, then you can use ViewScopeManager#isUnloadRequest(HttpServletRequest) or ViewScopeManager#isUnloadRequest(FacesContext), depending on whether the FacesContext is available in the current context. You should always ensure that the flow just continues for them, else the unload requests won't be able to do their work of explicitly destroying the bean and state.