Tomcat解决静态文件DefaultServlet分析
问题的原因是,使用网页打开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);
- 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 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);
- }
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Tomcat解决静态文件DefaultServlet分析