[Spring Boot] response header新增no-cache

Jeff Hsieh
6 min readFeb 21, 2019

因為系統趕上線的關係好久沒更新了,趁著推版的空檔簡單記錄一下昨天做的事情

之前發生過一個問題是行情的資料在頁面上看起來是舊的,打了 API 網址後看到的資料卻是正常的,後來釐清後發現網頁打的 API domain 中間接了 CDN,而 CDN 設定了 cache 所以才會拿到舊的資料,之後把 CDN 設定拿掉並清除 cache 後就正常了

然而和主管討論後決定,除了 CDN 這邊不要設定 cache 外,Server 端在 Response 的 Header 也應該要加上 cache-control: no-cache,這樣就算後面不小心開啟 cache 也不會造成問題

所以就開始 survey 囉!

由於 cache 應該是很常用的功能,而後端採用 Spring Boot,框架應該會有機制處理這類的需求,於是到官方文件中去看 application.properties 的設定有沒有包含這一塊,果然看到了其中有spring.resources.cache.cachecontrol.no-cache= 的設定,但是加上去後回應的 header 卻沒有跑出想要的結果

接下來看到的是用WebMvcConfigurer 的 ResourceHandlerRegistry 的方式,而這個 ResourceHandlerRegistry 之中剛好有 setCacheControl 的 function,想必就是這個了吧!

於是便著手在 @Configuration 標記的那份 config 檔中新增這些設定,完成的程式碼如下:

@Bean
public WebMvcConfigurer webConfigurer () {
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers (ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*")
.addResourceLocations("/*")
.setCacheControl(CacheControl.noCache());
}
};
}

但事情沒有這麼簡單,看起來沒什麼問題跑起來卻一樣沒有作用,於是就卡關了

然而仔細端詳程式碼會發現,在 setCacheControl 之前還有一行叫做addResourceLocations,網路上的範例中這個 location 會是 /static/* 之類的內容,而且在前面提到的spring.resources.cache.cachecontrol.no-cache= 之中,也有 resources 的關鍵字在

該不會…這些都是靜態資源檔的設定吧?

查過以後的確如此,這些 cache 的設定都是給靜態資源檔使用的,讓圖片等資源可以被 cache 起來,而我們的 Server 只會回傳 RestfulAPI 而已,這樣的設定自然不會生效

所以換個方式去搜尋,去找看看要怎麼修改每個 response 的 header,在每個controller前做設定的方式就不考慮了,我們需要的是一個通用的設定,最後就找到了所謂的Filter

Filter 可以說是 AOP 概念的一種實現,和 Interceptor 有些類似,但 Filter 是由 Servlet Container 管理,而 Interceptor 是獨立存在的,而之所以會選擇 Filter 則是一種概念上的問題

Filter 和 Interceptor 同樣都可以做到我們的需求,但 Filter 綁定在 Servlet 之上,設計的用意主要是用來處理 Request 和 Response,而 Interceptor 則不一定只能用在 Web 層中,且 Interceptor 比較有種「中斷處理」的概念,對於我們要加上某種東西的需求來說,Filter 會更適合一些

最後就是實作啦,這部分就沒有什麼特別的了直接上 code,首先先寫 Filter,因為不想再多寫個檔案所以直接寫在 Config 中用 Inner class:

class HeaderFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {

}

@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Cache-Control", "no-cache");
chain.doFilter(req, res);
}

@Override
public void destroy() {

}
}

然後新增 config 設定:

@Bean
public FilterRegistrationBean headerFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new HeaderFilter());
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}

搞定!

Postman result

參考資料:
https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
https://stackoverflow.com/questions/33214501/how-to-add-cache-control-header-to-static-resource-in-spring-boot
https://stackoverflow.com/questions/23381648/interceptors-or-filters
https://www.cnblogs.com/paddix/p/8365558.html
https://www.techiedelight.com/add-custom-header-to-all-responses-spring-boot/

--

--

Jeff Hsieh

資深後端工程師,熟悉JAVA與現代網頁後端技術與框架,並具備金融市場交易知識