5

I'm trying to set up a project to not cache static assets .css and .js. We seem to be having some internal caching issues for some people and I'm hoping this clears it up.

I have in place a phase listener, basically a slightly modified version of this http://turbomanage.wordpress.com/2006/08/08/disable-browser-caching-in-jsf/

My class:

package com.ods.common.jsf.phaselistener;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletResponse;

public class CacheControlPhaseListener implements PhaseListener
{
public PhaseId getPhaseId()
{
    return PhaseId.RENDER_RESPONSE;
}

public void afterPhase(PhaseEvent event)
{
}

public void beforePhase(PhaseEvent event)
{
    FacesContext facesContext = event.getFacesContext();
    HttpServletResponse response = (HttpServletResponse) facesContext
            .getExternalContext().getResponse();
    response.setHeader("Cache-control", "no-cache"); // HTTP 1.1
    response.setHeader("Cache-control", "no-store"); // HTTP 1.1
    response.setHeader("Cache-control", "must-revalidate"); // HTTP 1.1
    // response.setHeader("Pragma","no-cache"); //HTTP 1.0
    response.setHeader("Allow", "GET"); // Allowing GET Method only
    response.setHeader("Allow", "POST");// Allowing POST Method only
    response.setDateHeader("Expires", -1); // prevent caching at the proxy server

            /*what I've added*/
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Content-type", "x-javascript");
            //below are the content types I'm seeing for the css/js assets via Firebug
    response.setHeader("Content-type", "application/x-javascript");
    response.setHeader("Content-type", "text/css");
}
}

My addition to faces-config.xml:

<lifecycle>
    <phase-listener id="nocache">com.ods.common.jsf.phaselistener.CacheControlPhaseListener</phase-listener>
</lifecycle>

My .xhtml page is getting an expires header in the past:

Expires Thu, 01 Jan 1970 00:00:00 GMT

So this seems to be working somewhat...I'm assuming this date is from the Expires -1 (setting it to the unix epoch).

As you can see, I've tried setting appropriate content-type headers for some javascript and css but the expire dates on those assets are a week into the future.

Anyone have any ideas? Also, I'm a front-end developer, not a back-end java guy. I can mess around with Java, but I'm defintiely not a java developer. This is my first job with JSF as well so as much as you can dumb down would be good :)

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
magenta placenta
  • 1,461
  • 7
  • 22
  • 34

1 Answers1

15

With the setHeader() you're overridding any previously set header. Rather use addHeader() instead, or just put all values commaseparated as the header value. Here's the complete set:

response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.

Your another mistake is that a PhaseListener isn't the best place for this. It's only invoked on JSF page requests, not on static resource requests which are independently invoked by the webbrowser. In other words, only the JSF page itself has caching disabled, but all <script>, <link>, <img>, etc will generate new requests which doesn't invoke that PhaseListener because those are not JSF pages.

Rather use a Filter.

@WebFilter("/*")
public class NoCacheFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
        response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
        response.setDateHeader("Expires", 0); // Proxies.
        chain.doFilter(req, res);
    }

    // ... (just keep init() and destroy() NO-OP)
}

If you target a Servlet 3.0 container (Tomcat 7, Glassfish 3, etc), then web.xml (or faces-config.xml) registration is not necessary. The @WebFilter("/*") will autoregister it and map it on an URL pattern of /* which thus covers all requests.

See also:


Unrelated to the concrete problem, disabling the static asset caching altogether isn't the best idea. It unnecessarily costs network bandwidth. Rather look for a different solution, for example including the server startup timestamp in the query string.

E.g.

<script src="foo.js?#{startup.time}"></script>

with in faces-config.xml

<managed-bean>
    <managed-bean-name>startup</managed-bean-name>
    <managed-bean-class>java.util.Date</managed-bean-class>
    <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

This example would force the browser to reload the assets whenever the server has restarted.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • If I want just .css and .js files, would I need two separate filters? @WebFilter("/path/to/css") and @WebFilter("/path/to/javascript") – magenta placenta Jun 01 '12 at 17:18
  • 2
    Use `@WebFilter(urlPatterns={"*.js","*.css"})`. – BalusC Jun 01 '12 at 17:19
  • 1
    Note: for Servlet 2.5 you of course need the old fashioned `web.xml` registration. See also http://stackoverflow.com/tags/servlet-filters/info for a concrete example. – BalusC Jun 01 '12 at 22:53
  • Setting headers on resources and customize their handling, is actually the job of `javax.faces.application.Resource`. I would advise registering a `ResourceHandler` that overrides custom `Resource` handling. See also [custom `ResourceHandler`](https://stackoverflow.com/a/18146091/744133), and [Mojarra Implementation](https://github.com/javaserverfaces/mojarra/blob/master/impl/src/main/java/com/sun/faces/application/resource/ResourceImpl.java) – YoYo Oct 20 '18 at 00:54
  • @YoYo: not all static resources go through the `FacesServlet`. – BalusC Oct 21 '18 at 15:15
  • It might not be handled by a servlet container at all in a real production environment. – YoYo Oct 22 '18 at 01:43