web项目一旦部署完毕,项目中的图片、CSS以及JS基本上很少发生变动,那么假如把这些组件缓存在浏览器客户端,而不再从服务器上获取,那么网站的访问者在首次访问网站后,后续的请求将会大量减轻服务器的请求压力。这一举动,带来的性能提升,可以称作完美!那么如何做呢?那就是为组件添加Expires(期限)头!
一、了解Expires头
起初,在读《高性能web建站指南》第三章“添加Expires头”时,感觉很有压力,因为不了解Expires头是做什么用的,所以就没有心思去实践该做法,就搁置了一段时间。然而今天心情很好,耐着性子在网上找一系列的资源,并且重读这篇文章,实践再三,终于搞定,特此分享,希望更多的朋友能够看到,实践到你的项目中,从而提升网站性能。
我提供几篇文章大家读一读:
tomcat7官方doc中给出的Expires_Filter,可结合源码进行分析。
浏览器缓存详细解析
HTTP1.1 协议
网站性能优化:cache-control设置详解
有了这几篇文章作为铺垫,我想你基本上就可以了解Expires头啦。
二、效果
对于一个添加了Expires头的css来说,其请求的信息如图,多了max-age,以及Expires,这两者之间的关系我也不再赘述,之前的文章中描述比较清楚。
三、实现方法
①、新建CacheControlFilter.java
package com.honzh.common.filter; import java.io.IOException; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class CacheControlFilter implements Filter { private FilterConfig config = null; private static final String CACHE_CONTROL_BY_TYPE = "Cache-Control"; private static final String HEADER_EXPIRES = "Expires"; private static final Log log = LogFactory.getLog(CacheControlFilter.class); public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { HttpServletResponse httpResponse = (HttpServletResponse) response; HttpServletRequest httpRequest = (HttpServletRequest) request; boolean cacheControlSet = false; for (Enumeration<String> names = config.getInitParameterNames(); names.hasMoreElements();) { String headerName = (String) names.nextElement(); log.debug("参数名称:" + headerName); String value = config.getInitParameter(headerName); log.debug("参数值:" + value); // 说明包含了类型,此时要筛选符合条件的类型 String type = headerName.substring(CACHE_CONTROL_BY_TYPE.length()).trim(); log.debug("参数规定的类型:" + type); String contentType = httpRequest.getHeader("Accept"); log.debug("请求内容类型为:" + contentType); if (contains(contentType, ";")) { // lookup content-type without charset match (e.g. // "text/html") String contentTypeWithoutCharset = substringBefore(contentType, ";").trim(); if (contentTypeWithoutCharset.indexOf(type) != -1) { // 类型匹配,再看值是不是max-age,如果是,将max-age设置为当前时间+1个月 Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(Calendar.MONTH, 1); Date expirationDate = calendar.getTime(); httpResponse.setDateHeader(HEADER_EXPIRES, expirationDate.getTime()); String maxAgeDirective = "max-age=" + ((expirationDate.getTime() - System.currentTimeMillis()) / 1000); setControlHeader(httpResponse, "private," + maxAgeDirective); cacheControlSet = true; } } } if (!cacheControlSet) { setControlHeader(httpResponse, "private"); } } chain.doFilter(request, response); // resp.setHeader("Expires", "Tue, 03 Jul 2001 06:00:00 GMT"); // resp.setDateHeader("Last-Modified", new Date().getTime()); // resp.setHeader("Cache-Control", // "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0"); // resp.setHeader("Cache-Control", "private"); // resp.setHeader("Pragma", "no-cache"); } private void setControlHeader(HttpServletResponse httpResponse, String cache_control) { String cacheControlHeader = httpResponse.getHeader(CACHE_CONTROL_BY_TYPE); String newCacheControlHeader = (cacheControlHeader == null) ? cache_control : cacheControlHeader + ", " + cache_control; httpResponse.setHeader(CACHE_CONTROL_BY_TYPE, newCacheControlHeader); } @Override public void destroy() { } @Override public void init(FilterConfig config) throws ServletException { this.config = config; } protected static boolean contains(String str, String searchStr) { if (str == null || searchStr == null) { return false; } return str.indexOf(searchStr) >= 0; } protected static String substringBefore(String str, String separator) { if (str == null || str.isEmpty() || separator == null) { return null; } if (separator.isEmpty()) { return ""; } int separatorIndex = str.indexOf(separator); if (separatorIndex == -1) { return str; } return str.substring(0, separatorIndex); } }
web.xml中我们为Expires设置了三种类型,分别为image、css、js,②中有详细参数,这样在filter中,我们将为这三种类型添加Expires、max-age。
至于类中提到的private,你可以关注我的另一篇文章gzip压缩tomcat服务器响应包,大幅提升web性能。
其他方面,代码不复杂,主要的是做法,我将做法提供给大家。
②、web.xml