Tomcat处理静态文件DefaultServlet分析
2014-04-16 00:38
369 查看
问题的起因是,用网页打开tomcat7服务器上一个只有静态内容的jsp页面,里面链接了gif文件,F5刷新的时候,css和gif文件请求返回304 not Mofified 头,而jsp请求还是返回200(想搞破坏,把jsp也可以在通过304返回头直接读取客户端缓存,但jsp是servlet只能在servlet容器中运行。。。)
因为jsp文件请求时tomcat的JspServlet处理的,而css和html、gif等静态文件默认是tomcat的DefaultServlet处理的(在tomcat配置文件conf/web.xml中有配置)。
先来看下tomcat7的DefaultServlet的源码:
服务端通过resource.lookupCache(path)从服务端缓存中读取资源获得CacheEntry,如果请求资源不存在CacheEntry.exists为false,则返回404。然后通过checkIfHeaders(request, response, cacheEntry.attributes)方法根据客户端请求头的If-None-Match,If-Modified-Since来判断请求资源是否被修改,如果未被修改,则返回304头,户端直接从客户端缓存中读取资源文件。如果第一次访问或者ctrl+F5强制刷新或者资源已修改过,默认返回Etag和Last-Modified头,,告知客户端下次访问可以通过If-None-Match,If-Modified-Since比较返回304来判断是否可使用客户端缓存。还有设置文件内容,content-length,range头等,最后输出内容:
if (!checkSendfile(request, response, cacheEntry, contentLength, null))
copy(cacheEntry, renderResult, ostream);
如果文件超过48k,判断是否使用sendfile来输出大文件。
请求头中通过range请求部分下载,则:
if (!checkSendfile(request, response, cacheEntry, range.end - range.start + 1, range))
copy(cacheEntry, ostream, range);
首先从存在的资源缓存cache中查找,未找到则从不存在的资源的缓存notFoundCache中查找。如果找到,检查缓存是否有效。cache默认有效期5秒,5秒之内不检查原文件是否有修改,超过有效期,需要验证原文件的lastModified和ContendLenth和缓存中的是否一致,不一致清除缓存,一致则更新缓存的timestap再次5秒有效期。(这样的话,如果修改css或js等静态文件,如果测试的人一直访问(5秒间隔内)这个页面,导致静态文件一直从服务端缓存中读取,那样无论是否强制刷新修改都不会生效啊。)未找到则生成CacheEntry,加载到相应的cache 或notFoundCache中。当然,nonCacheable数组默认/WEB-INF/lib/,
/WEB-INF/classes/路径下文件都不从缓存获取。这里cache缓存设计成一个有序数组,而notFoundCache设计为一个HashMap,cache操作稍微复杂点。难道是因为cache释放内存需要更细粒度的控制?
因为jsp文件请求时tomcat的JspServlet处理的,而css和html、gif等静态文件默认是tomcat的DefaultServlet处理的(在tomcat配置文件conf/web.xml中有配置)。
先来看下tomcat7的DefaultServlet的源码:
服务端通过resource.lookupCache(path)从服务端缓存中读取资源获得CacheEntry,如果请求资源不存在CacheEntry.exists为false,则返回404。然后通过checkIfHeaders(request, response, cacheEntry.attributes)方法根据客户端请求头的If-None-Match,If-Modified-Since来判断请求资源是否被修改,如果未被修改,则返回304头,户端直接从客户端缓存中读取资源文件。如果第一次访问或者ctrl+F5强制刷新或者资源已修改过,默认返回Etag和Last-Modified头,,告知客户端下次访问可以通过If-None-Match,If-Modified-Since比较返回304来判断是否可使用客户端缓存。还有设置文件内容,content-length,range头等,最后输出内容:
if (!checkSendfile(request, response, cacheEntry, contentLength, null))
copy(cacheEntry, renderResult, ostream);
如果文件超过48k,判断是否使用sendfile来输出大文件。
请求头中通过range请求部分下载,则:
if (!checkSendfile(request, response, cacheEntry, range.end - range.start + 1, range))
copy(cacheEntry, ostream, range);
protected void serveResource(HttpServletRequest request, HttpServletResponse response, boolean content) throws IOException, ServletException { boolean serveContent = content; // Identify the requested resource path String path = getRelativePath(request); if (debug > 0) { if (serveContent) log("DefaultServlet.serveResource: Serving resource '" + path + "' headers and data"); else log("DefaultServlet.serveResource: Serving resource '" + path + "' headers only"); } // 从服务端缓存中读取资源 CacheEntry cacheEntry = resources.lookupCache(path); //请求资源不存在,则返回404 if (!cacheEntry.exists) { // Check if we're included so we can return the appropriate // missing resource name in the error String requestUri = (String) request.getAttribute( RequestDispatcher.INCLUDE_REQUEST_URI); if (requestUri == null) { requestUri = request.getRequestURI(); } else { // We're included // SRV.9.3 says we must throw a FNFE throw new FileNotFoundException( sm.getString("defaultServlet.missingResource", requestUri)); } response.sendError(HttpServletResponse.SC_NOT_FOUND, requestUri); return; } // If the resource is not a collection, and the resource path // ends with "/" or "\", return NOT FOUND if (cacheEntry.context == null) { if (path.endsWith("/") || (path.endsWith("\\"))) { // Check if we're included so we can return the appropriate // missing resource name in the error String requestUri = (String) request.getAttribute( RequestDispatcher.INCLUDE_REQUEST_URI); if (requestUri == null) { requestUri = request.getRequestURI(); } response.sendError(HttpServletResponse.SC_NOT_FOUND, requestUri); return; } } boolean isError = response.getStatus() >= HttpServletResponse.SC_BAD_REQUEST; // Check if the conditions specified in the optional If headers are // satisfied. if (cacheEntry.context == null) { // Checking If headers boolean included = (request.getAttribute( RequestDispatcher.INCLUDE_CONTEXT_PATH) != null); //checkIfHeaders根据客户端请求头的If-None-Match,If-Modified-Since //来判断请求资源是否被修改,如果未被修改,则返回304头 //客户端直接从客户端缓存中读取资源文件。 if (!included && !isError && !checkIfHeaders(request, response, cacheEntry.attributes)) { return; } } // Find content type. String contentType = cacheEntry.attributes.getMimeType(); if (contentType == null) { contentType = getServletContext().getMimeType(cacheEntry.name); cacheEntry.attributes.setMimeType(contentType); } ArrayList<Range> ranges = null; long contentLength = -1L; if (cacheEntry.context != null) { // Skip directory listings if we have been configured to // suppress them if (!listings) { response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI()); return; } contentType = "text/html;charset=UTF-8"; } else { if (!isError) { //第一次访问或者ctrl+F5强制刷新或者前面资源已修改过,静态文件处理 //默认返回Etag和Last-Modified,告知客户端下次访问可以 //通过If-None-Match,If-Modified-Since比较返回304来判断是否可使用客户端缓存 if (useAcceptRanges) { // Accept ranges header response.setHeader("Accept-Ranges", "bytes"); } // Parse range specifier ranges = parseRange(request, response, cacheEntry.attributes); // ETag header response.setHeader("ETag", cacheEntry.attributes.getETag()); // Last-Modified header response.setHeader("Last-Modified", cacheEntry.attributes.getLastModifiedHttp()); } // Get content length contentLength = cacheEntry.attributes.getContentLength(); // Special case for zero length files, which would cause a // (silent) ISE when setting the output buffer size if (contentLength == 0L) { serveContent = false; } } ServletOutputStream ostream = null; PrintWriter writer = null; if (serveContent) { // Trying to retrieve the servlet output stream try { ostream = response.getOutputStream(); } catch (IllegalStateException e) { // If it fails, we try to get a Writer instead if we're // trying to serve a text file if ( (contentType == null) || (contentType.startsWith("text")) || (contentType.endsWith("xml")) || (contentType.contains("/javascript")) ) { writer = response.getWriter(); // Cannot reliably serve partial content with a Writer ranges = FULL; } else { throw e; } } } // Check to see if a Filter, Valve of wrapper has written some content. // If it has, disable range requests and setting of a content length // since neither can be done reliably. ServletResponse r = response; long contentWritten = 0; while (r instanceof ServletResponseWrapper) { r = ((ServletResponseWrapper) r).getResponse(); } if (r instanceof ResponseFacade) { contentWritten = ((ResponseFacade) r).getContentWritten(); } if (contentWritten > 0) { ranges = FULL; } if ( (cacheEntry.context != null) || isError || ( ((ranges == null) || (ranges.isEmpty())) && (request.getHeader("Range") == null) ) || (ranges == FULL) ) { // Set the appropriate output headers if (contentType != null) { if (debug > 0) log("DefaultServlet.serveFile: contentType='" + contentType + "'"); response.setContentType(contentType); } if ((cacheEntry.resource != null) && (contentLength >= 0) && (!serveContent || ostream != null)) { if (debug > 0) log("DefaultServlet.serveFile: contentLength=" + contentLength); // Don't set a content length if something else has already // written to the response. if (contentWritten == 0) { if (contentLength < Integer.MAX_VALUE) { response.setContentLength((int) contentLength); } else { // Set the content-length as String to be able to use a // long response.setHeader("content-length", "" + contentLength); } } } InputStream renderResult = null; if (cacheEntry.context != null) { if (serveContent) { // Serve the directory browser renderResult = render(getPathPrefix(request), cacheEntry); } } // Copy the input stream to our output stream (if requested) if (serveContent) { try { response.setBufferSize(output); } catch (IllegalStateException e) { // Silent catch } if (ostream != null) { //这里是content输出,判断是否使用sendfile来输出大文件 if (!checkSendfile(request, response, cacheEntry, contentLength, null)) copy(cacheEntry, renderResult, ostream); } else { copy(cacheEntry, renderResult, writer); } } } else { // if ((ranges == null) || (ranges.isEmpty())) return; // Partial content response. response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); if (ranges.size() == 1) { Range range = ranges.get(0); response.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length); long length = range.end - range.start + 1; if (length < Integer.MAX_VALUE) { response.setContentLength((int) length); } else { // Set the content-length as String to be able to use a long response.setHeader("content-length", "" + length); } if (contentType != null) { if (debug > 0) log("DefaultServlet.serveFile: contentType='" + contentType + "'"); response.setContentType(contentType); } if (serveContent) { try { response.setBufferSize(output); } catch (IllegalStateException e) { // Silent catch } if (ostream != null) { //Range来实现资源文件部分内容传输 if (!checkSendfile(request, response, cacheEntry, range.end - range.start + 1, range)) copy(cacheEntry, ostream, range); } else { // we should not get here throw new IllegalStateException(); } } } else { response.setContentType("multipart/byteranges; boundary=" + mimeSeparation); if (serveContent) { try { response.setBufferSize(output); } catch (IllegalStateException e) { // Silent catch } if (ostream != null) { copy(cacheEntry, ostream, ranges.iterator(), contentType); } else { // we should not get here throw new IllegalStateException(); } } } } }静态文件优先从缓存中读取:
首先从存在的资源缓存cache中查找,未找到则从不存在的资源的缓存notFoundCache中查找。如果找到,检查缓存是否有效。cache默认有效期5秒,5秒之内不检查原文件是否有修改,超过有效期,需要验证原文件的lastModified和ContendLenth和缓存中的是否一致,不一致清除缓存,一致则更新缓存的timestap再次5秒有效期。(这样的话,如果修改css或js等静态文件,如果测试的人一直访问(5秒间隔内)这个页面,导致静态文件一直从服务端缓存中读取,那样无论是否强制刷新修改都不会生效啊。)未找到则生成CacheEntry,加载到相应的cache 或notFoundCache中。当然,nonCacheable数组默认/WEB-INF/lib/,
/WEB-INF/classes/路径下文件都不从缓存获取。这里cache缓存设计成一个有序数组,而notFoundCache设计为一个HashMap,cache操作稍微复杂点。难道是因为cache释放内存需要更细粒度的控制?
protected CacheEntry cacheLookup(String lookupName) { if (cache == null) return (null); String name; if (lookupName == null) { name = ""; } else { name = lookupName; } //无法被缓存的资源:/WEB-INF/lib/, /WEB-INF/classes/ for (int i = 0; i < nonCacheable.length; i++) { if (name.startsWith(nonCacheable[i])) { return (null); } } // CacheEntry cacheEntry = cache.lookup(name); if (cacheEntry == null) { cacheEntry = new CacheEntry(); cacheEntry.name = name; // Load entry cacheLoad(cacheEntry); } else { /*cache有效期5秒,5秒之内不检查原文件是否有修改,超过有效期, 需要验证原文件的lastModified和ContendLenth和缓存中的是否一致, 不一致清除缓存,一致则更新缓存的timestap再次5秒有效期。*/ if (!validate(cacheEntry)) { if (!revalidate(cacheEntry)) { cacheUnload(cacheEntry.name); return (null); } else { cacheEntry.timestamp = System.currentTimeMillis() + cacheTTL; } } cacheEntry.accessCount++; } return (cacheEntry); }
相关文章推荐
- Tomcat处理静态文件DefaultServlet分析(转载)
- nginx对静态文件cache的处理分析
- SpringMVC处理静态文件源码分析
- tomcat的defaultServlet--用于处理静态资料的
- SpringMVC处理静态文件源码分析
- SpringMVC处理静态文件源码分析
- SpringMVC处理静态文件源码分析
- JavaWeb项目中springmvc和tomcat对静态文件的处理
- SpringMVC处理静态文件源码分析
- nginx配置2个tomcat的负载均衡+配置静态文件处理(图片等)
- nginx对静态文件cache的处理分析
- 超详细的django1.8处理centos下nginx上处理静态文件步骤!
- 一个简单的文件处理--16进制数据统计分析
- spring3mvc框架开发中resin和tomcat默认servelt配置处理静态资源
- Tomcat处理HTTP请求源码分析(上)
- inetd.c源代码分析之处理inetd.conf文件的结果
- django1.4 关于处理静态文件的问题
- 让Nginx处理Django的静态文件
- tomcat对sessionId的处理分析
- spring mvc处理静态文件