tomcat和jetty对静态资源的处理和客户端缓存的处理
原文链接:http://www.javaarch.net/jiagoushi/867.htm
这两个默认servlet名称都是defaultservlet,然后在web.xml中就可以添加下面的配置让应用支持都静态资源的处理,对应的这些静态资源的目录则是在webapp根目录下,这里其实可以不用配置servlet名称,对于名称为default的url,tomcat和jetty都会作为静态资源文件处理
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.gif</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.swf</url-pattern> </servlet-mapping>
那么我们来看看tomcat和jetty对静态资源的客户端缓存的处理逻辑:
tomcat,tomcat在default的servlet支持一些参数,如果有需要那么就需要配置servlet了,
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
在对于静态资源处理的客户端缓存的代码如下
// ETag header response.setHeader("ETag", cacheEntry.attributes.getETag()); // Last-Modified header response.setHeader("Last-Modified", cacheEntry.attributes.getLastModifiedHttp());
这里的etag计算规则如下:
long contentLength = getContentLength(); long lastModified = getLastModified(); if ((contentLength >= 0) || (lastModified >= 0)) { weakETag = "W/\"" + contentLength + "-" + lastModified + "\""; }
输出的reponse header如下:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"7482-1371188756000" Last-Modified: Fri, 14 Jun 2013 05:45:56 GMT Content-Type: text/css;charset=GBK Content-Length: 7482 Date: Sun, 16 Jun 2013 07:05:37 GMT
第二次请求时,会先对reqeust的etag和last-modified进行比对,如果没更新的话则返回304
protected ArrayList<Range> parseRange(HttpServletRequest request, HttpServletResponse response, ResourceAttributes resourceAttributes) throws IOException { // Checking If-Range String headerValue = request.getHeader("If-Range"); if (headerValue != null) { long headerValueTime = (-1L); try { headerValueTime = request.getDateHeader("If-Range"); } catch (IllegalArgumentException e) { // Ignore } String eTag = resourceAttributes.getETag(); long lastModified = resourceAttributes.getLastModified(); if (headerValueTime == (-1L)) { // If the ETag the client gave does not match the entity // etag, then the entire entity is returned. if (!eTag.equals(headerValue.trim())) return FULL; } else { // If the timestamp of the entity the client got is older than // the last modification date of the entity, the entire entity // is returned. if (lastModified > (headerValueTime + 1000)) return FULL; } }
第二次返回:
HTTP/1.1 304 Not Modified Server: Apache-Coyote/1.1 ETag: W/"2640-1371187966000" Date: Sun, 16 Jun 2013 07:23:27 GMT
那么这里对静态资源浏览器就自动能够缓存起来了。当然tomcat和jetty服务器也会对静态资源进行缓存。
jetty对这个处理也差不多,不过jetty对于静态资源的缓存策略可以做更多参数设置,这些参数都是在web.xml配置servlet的时候可以进行设置的。
_cacheControl=getInitParameter("cacheControl"); String resourceCache = getInitParameter("resourceCache"); int max_cache_size=getInitInt("maxCacheSize", -2); int max_cached_file_size=getInitInt("maxCachedFileSize", -2); int max_cached_files=getInitInt("maxCachedFiles", -2); if (resourceCache!=null) { if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2) LOG.debug("ignoring resource cache configuration, using resourceCache attribute"); if (_relativeResourceBase!=null || _resourceBase!=null) throw new UnavailableException("resourceCache specified with resource bases"); _cache=(ResourceCache)_servletContext.getAttribute(resourceCache); LOG.debug("Cache {}={}",resourceCache,_cache); } _etags = getInitBoolean("etags",_etags);
处理代码:
写header
/* ------------------------------------------------------------ */ protected void writeHeaders(HttpServletResponse response,HttpContent content,long count) throws IOException { if (content.getContentType()!=null && response.getContentType()==null) response.setContentType(content.getContentType().toString()); if (response instanceof Response) { Response r=(Response)response; HttpFields fields = r.getHttpFields(); if (content.getLastModified()!=null) fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified()); else if (content.getResource()!=null) { long lml=content.getResource().lastModified(); if (lml!=-1) fields.putDateField(HttpHeader.LAST_MODIFIED,lml); } if (count != -1) r.setLongContentLength(count); writeOptionHeaders(fields); if (_etags) fields.put(HttpHeader.ETAG,content.getETag()); } else { long lml=content.getResource().lastModified(); if (lml>=0) response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml); if (count != -1) { if (count<Integer.MAX_VALUE) response.setContentLength((int)count); else response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count)); } writeOptionHeaders(response); if (_etags) response.setHeader(HttpHeader.ETAG.asString(),content.getETag().toString()); } }
判断静态资源是否修改过
/* ------------------------------------------------------------ */ /* Check modification date headers. */ protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content) throws IOException { try { if (!HttpMethod.HEAD.is(request.getMethod())) { if (_etags) { String ifm=request.getHeader(HttpHeader.IF_MATCH.asString()); if (ifm!=null) { boolean match=false; if (content!=null && content.getETag()!=null) { QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true); while (!match && quoted.hasMoreTokens()) { String tag = quoted.nextToken(); if (content.getETag().toString().equals(tag)) match=true; } } if (!match) { Response r = Response.getResponse(response); r.reset(true); r.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); return false; } } String ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString()); if (ifnm!=null && content!=null && content.getETag()!=null) { // Look for GzipFiltered version of etag if (content.getETag().toString().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag"))) { Response r = Response.getResponse(response); r.reset(true); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.getHttpFields().put(HttpHeader.ETAG,ifnm); return false; } // Handle special case of exact match. if (content.getETag().toString().equals(ifnm)) { Response r = Response.getResponse(response); r.reset(true); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.getHttpFields().put(HttpHeader.ETAG,content.getETag()); return false; } // Handle list of tags QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true); while (quoted.hasMoreTokens()) { String tag = quoted.nextToken(); if (content.getETag().toString().equals(tag)) { Response r = Response.getResponse(response); r.reset(true); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.getHttpFields().put(HttpHeader.ETAG,content.getETag()); return false; } } // If etag requires content to be served, then do not check if-modified-since return true; } } // Handle if modified since String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); if (ifms!=null) { //Get jetty's Response impl Response r = Response.getResponse(response); if (content!=null) { String mdlm=content.getLastModified(); if (mdlm!=null) { if (ifms.equals(mdlm)) { r.reset(true); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.flushBuffer(); return false; } } } long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); if (ifmsl!=-1) { if (resource.lastModified()/1000 <= ifmsl/1000) { r.reset(true); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.flushBuffer(); return false; } } } // Parse the if[un]modified dates and compare to resource long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString()); if (date!=-1) { if (resource.lastModified()/1000 > date/1000) { response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return false; } } } } catch(IllegalArgumentException iae) { if(!response.isCommitted()) response.sendError(400, iae.getMessage()); throw iae; } return true; }
第一次访问的response header头:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"7482-1371188756000" Last-Modified: Fri, 14 Jun 2013 05:45:56 GMT Content-Type: text/css;charset=GBK Content-Length: 7482 Date: Sun, 16 Jun 2013 07:05:37 GMT
第二次访问:
HTTP/1.1 304 Not Modified Server: Jetty(6.1.26)
不过处理静态资源大型网站肯定不是tomcat或者jetty,基本都是用apache或者nginx等来处理静态处理,性能更好。这里只是列出tomcat和jetyy对静态资源的处理和客户端缓存的支持。