Set cache control headers of static assets in Dropwizard
Asked Answered
W

4

7

What's the best way to set the cache control headers of static assets in a Dropwizard service?

Some Googling showed up an AssetsBundle constructor:

AssetsBundle(String resourcePath, com.google.common.cache.CacheBuilderSpec cacheBuilderSpec, String uriPath)

However on further investigation, it looks like the package com.yammer.dropwizard.bundles hasn't been part of Dropwizard since version 5.1.

Perhaps I'm missing something obvious, but is there a preferred way to handle this?

Wein answered 18/11, 2013 at 16:20 Comment(0)
G
2

Building on the answer from Tim Barclay, I created a filter which sets Cache-Control and Expires one year into the future, if the resource requested is a file with extension js, css, png, jpg, gif or svg. Otherwise the cache is disabled.

Hope it can be helpful for someone!

protected void setCacheHeaders(Environment environment, String urlPattern, int seconds) {
    FilterRegistration.Dynamic filter = environment.servlets().addFilter(
            "cacheControlFilter",
            new Filter() {
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {

                }

                @Override
                public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

                    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
                    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

                    String[] cacheFileTypes = {"js","css","png","jpg","gif","svg"};
                    String filetypeRequested = FilenameUtils.getExtension(httpServletRequest.getRequestURL().toString());

                    if (httpServletRequest.getMethod() == "GET" && seconds > 0 && Arrays.asList(cacheFileTypes).contains(filetypeRequested)) {
                        httpServletResponse.setHeader("Cache-Control", "public, max-age=" + seconds);
                        Calendar c = Calendar.getInstance();
                        c.setTime(new Date());
                        c.add(Calendar.SECOND, seconds);
                        SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz", Locale.US);
                        format.setTimeZone(TimeZone.getTimeZone("GMT"));
                        httpServletResponse.setHeader("Expires", format.format(c.getTime()));
                    } else {
                        httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
                        httpServletResponse.setHeader("Expires", "0");
                        httpServletResponse.setHeader("Pragma", "no-cache");
                    }

                    filterChain.doFilter(servletRequest, servletResponse);

                }

                @Override
                public void destroy() {

                }
            }
    );
    filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, urlPattern);
}

PS: I couldn't get the accepted answer's way to set the Expires-header to work:

resp.setHeader("Expires", new Date().getTime()+500000 + "");

Mine is terribly bloated in comparison, but it works:

Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.SECOND, seconds);
SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
httpServletResponse.setHeader("Expires", format.format(c.getTime()));
Gemot answered 7/4, 2016 at 8:39 Comment(1)
Based on the comment from @Rich on my answer, it looks like maybe what I suggested no longer works with current versions of Dropwizard. I haven't used it (or Java generally in fact) since 2014 so I can't verify.Wein
W
10

In case anyone is interested (which, judging by the number of views this question has had, they probably aren't) this is how I solved this.

I created a CacheControlFilter class in the same package as my Service class:

public class CacheControlFilter implements Filter{

    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

        HttpServletResponse resp = (HttpServletResponse) response;

        // Add whatever headers you want here
        resp.setHeader("Cache-Control", "public, max-age=500000");
        resp.setHeader("Expires", new Date().getTime()+500000 + "");

        chain.doFilter(request, response);
    }

    public void destroy() {}

    public void init(FilterConfig arg0) throws ServletException {}
}

Then in the service class, just add the line:

env.addFilter(new CacheControlFilter(), "/*");

Of course you could be more finegrained and add a different filter for, say, image files and css files but this adds the headers for all requests.

From Comments :

for dropwizard 1.0.6. Just register with

env.servlets()
   .addFilter("MyFilter", new CacheControlFilter())
   .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*") 
Wein answered 4/12, 2013 at 16:20 Comment(2)
This helped me, thanks. But it should be noted that this filter, as written (and installed) applies to ALL requests of every kind (GET, PUT, etc.). It does NOT just apply to the serving of static assets.Tinnitus
Works as of 1.0.6. Just register with env.servlets().addFilter("MyFilter", CacheControlFilter()).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*")Ln
E
2

In case you just want to clear the cache for every request, below is my solution, which uses CacheBustingFilter, provided by DropWizard.

  1. Define a custom Configuration, in my case its WebConfiguration. Use this configuration while setting up your DropWizard Application.

    public class WebConfiguration extends Configuration {
    
    @JsonProperty
    private String enableCacheControl;
    
    public String getEnableCacheControl() {
        return enableCacheControl;
    }
    
    public void setEnableCacheControl(String enableCacheControl) {
        this.enableCacheControl = enableCacheControl;
    }
    

    }

  2. Get the configuration you defined in #1 and register the CacheBustingFilter based on its value.

Add this in your run method -

    // get the cache control settings from the YAML - configuration
    String enableCacheControl = configuration.getEnableCacheControl();
    boolean enableCacheBustingFilter = Boolean.parseBoolean(enableCacheControl);

    if(enableCacheBustingFilter){
        // caching was enabled in YAML - was set to true - enabling the cacheBustingFilter
        // this will ALWAYS return  "must-revalidate,no-cache,no-store" in the Cache-Control response header
        environment.servlets().addFilter("CacheBustingFilter", new CacheBustingFilter())
            .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
    }
Eyeopening answered 14/10, 2015 at 22:22 Comment(1)
Thanks -- this works! I was missing off the "addMappingForUrlPatterns", without which this silently does nothing :-(Hixon
G
2

Building on the answer from Tim Barclay, I created a filter which sets Cache-Control and Expires one year into the future, if the resource requested is a file with extension js, css, png, jpg, gif or svg. Otherwise the cache is disabled.

Hope it can be helpful for someone!

protected void setCacheHeaders(Environment environment, String urlPattern, int seconds) {
    FilterRegistration.Dynamic filter = environment.servlets().addFilter(
            "cacheControlFilter",
            new Filter() {
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {

                }

                @Override
                public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

                    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
                    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

                    String[] cacheFileTypes = {"js","css","png","jpg","gif","svg"};
                    String filetypeRequested = FilenameUtils.getExtension(httpServletRequest.getRequestURL().toString());

                    if (httpServletRequest.getMethod() == "GET" && seconds > 0 && Arrays.asList(cacheFileTypes).contains(filetypeRequested)) {
                        httpServletResponse.setHeader("Cache-Control", "public, max-age=" + seconds);
                        Calendar c = Calendar.getInstance();
                        c.setTime(new Date());
                        c.add(Calendar.SECOND, seconds);
                        SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz", Locale.US);
                        format.setTimeZone(TimeZone.getTimeZone("GMT"));
                        httpServletResponse.setHeader("Expires", format.format(c.getTime()));
                    } else {
                        httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
                        httpServletResponse.setHeader("Expires", "0");
                        httpServletResponse.setHeader("Pragma", "no-cache");
                    }

                    filterChain.doFilter(servletRequest, servletResponse);

                }

                @Override
                public void destroy() {

                }
            }
    );
    filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, urlPattern);
}

PS: I couldn't get the accepted answer's way to set the Expires-header to work:

resp.setHeader("Expires", new Date().getTime()+500000 + "");

Mine is terribly bloated in comparison, but it works:

Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.SECOND, seconds);
SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
httpServletResponse.setHeader("Expires", format.format(c.getTime()));
Gemot answered 7/4, 2016 at 8:39 Comment(1)
Based on the comment from @Rich on my answer, it looks like maybe what I suggested no longer works with current versions of Dropwizard. I haven't used it (or Java generally in fact) since 2014 so I can't verify.Wein
M
0

In newer versions of Dropwizard @CacheControl annotation.

https://dropwizardio.readthedocs.io/en/v0.7.0/manual/core.html#caching

Manado answered 11/2, 2020 at 4:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.