Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)
Posted 徐同学呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)相关的知识,希望对你有一定的参考价值。
一、前言
Tomcat根据Servlet
规范实现了Servlet容器,同时也具备HTTP服务器的功能。而Servlet
在Tomcat中可以分为三种:普通Servlet
、JspServlet
和DefaultServlet
。普通Servlet
就是使用者自己根据业务定义的Servlet
,后面两个Tomcat已经通过继承HttpServlet
实现,JspServlet
处理jsp页面,DefaultServlet
处理静态资源。
一个请求到达Tomcat后将由URI映射器根据请求URI进行建模,计算出该请求该发往哪个Host容器的哪个Context容器的哪个Wrapper处理,在路由到Wrapper容器时会通过一定的算法选择不同的Servlet进行处理,最后找不到对应的Servlet处理时就会匹配到DefaultServlet
。
使用DefaultServlet
需要在web.xml里配置:
<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>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
二、DefaultServlet响应流程
DefaultServlet
会先获取对应路径的静态资源映射WebResource
,然后判断资源文件是否存在,可读,已修改等,最后读取文件内容响应给浏览器。
如果这个过程完全没有缓存的话,每次都进行io操作,可想这个性能是非常差的。Tomcat利用浏览器缓存,在第一次请求时,进行io操作读取文件内容返回给浏览器;第二次请求,如果文件没有修改过,则返回304状态码告知浏览器文件没有修改,可以直接从浏览器缓存中读取。第三次请求,此时文件修改了,则io读取文件内容返回给浏览器。
1、DefaultServlet如何判断文件是否修改
如下摘取DefaultServlet
的一段源码:
当判断获取的资源是文件时,会去判断请求的headers
(checkIfHeaders()
):
有四种判断headers的方法,因为默认Request Headers
中有If-Modified-Since
和If-None-Match
,所以暂时只看checkIfNoneMatch
。
首先判断request的headers中是否有If-None-Match
,有则判断headerValue
和eTag
是否相等,相等则说明文件没有修改,返回状态码304。
protected boolean checkIfNoneMatch(HttpServletRequest request,
HttpServletResponse response, WebResource resource)
throws IOException {
String eTag = resource.getETag();
String headerValue = request.getHeader("If-None-Match");
if (headerValue != null) {
boolean conditionSatisfied = false;
if (!headerValue.equals("*")) {
StringTokenizer commaTokenizer =
new StringTokenizer(headerValue, ",");
// 比较eTag eTag 是contentLength和lastModified根据一个算法生成的一个字符串
while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
String currentToken = commaTokenizer.nextToken();
if (currentToken.trim().equals(eTag))
// 二者相等,则没有修改
conditionSatisfied = true;
}
} else {
conditionSatisfied = true;
}
if (conditionSatisfied) {
if ( ("GET".equals(request.getMethod()))
|| ("HEAD".equals(request.getMethod())) ) {
// 没有修改,设置status=304,设置ETag给response的headers
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", eTag);
return false;
}
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
return true;
}
除了checkIfNoneMatch
其他三个方法的逻辑也很简单,核心思路就是通过比较文件的lastModified
或者contentLength+lastModified
是否和上次的一样,一样就是没有修改,不一样就是修改了。
整体思路就是,第一次请求,io读取文件内容,同时将Etag
和Last-Modified
设置到response里返回给浏览器,下次请求浏览器在请求头里回传If-Modified-Since
和If-None-Match
(内容上If-Modified-Since
对应Last-Modified
,If-None-Match
对应Etag
),DefaultServlet
响应时判断这两个参数和资源文件当前的值是否相等。
2、Tomcat对静态资源加缓存
Tomcat对静态资源做了一层封装WebResource
(接口),WebResource
对文件基础信息的获取进行了包装再调用,同时对io操作进行了封装,方便直接获取文件内容。Tomcat对WebResource
做了一层缓存(包括lastModified
、contentLength
、canRead
、isFile
等基础信息以及文件内容),默认缓存失效时长为5秒。获取WebResource
时先尝试获取CachedResource
,CachedResource
为空或者已经失效则获取FileResource
,FileResource
存在则加缓存CachedResource
,一个常规的缓存获取和更新操作。(CachedResource
和FileResource
都实现了接口WebResource
)
三、后台线程定时清理资源缓存
Tomcat中有一个后台线程(ContainerBackgroundProcessor
)默认每10秒判断一次静态资源的缓存是否失效,失效则删除;
每次请求时,如果获取到的是缓存,也会判断缓存是否失效,失效则清除缓存获取最新资源,最新资源存在则加缓存。
四、Q&A
1、浏览器已经缓存了静态资源的内容,为何Tomcat还要加一层缓存?
不止一个浏览器访问Tomcat。在静态资源没有修改的情况下,一个浏览器多次请求建立了浏览器缓存,另一个浏览器第一次请求没有浏览器缓存,但是Tomcat有缓存且没有失效,则可以直接获取文件内容,不需要进行文件的io操作。
2、Tomcat中静态资源的缓存失效时间为5秒,而后台线程定时清理是10秒,岂不是每次都被清理掉?
(1)清理缓存的操作不只有后台线程,在每次请求时,如果获取的缓存不为空,也会去判断缓存是否失效。
(2)后台线程的运行频率(
backgroundProcessorDelay=“10”
单位秒)可以配置,静态资源的缓存失效时间(cacheTtl="5000"
单位毫秒)也可以配置。
3、获取资源时,缓存没有失效,而资源已经修改,是否会重新读取资源内容?
虽然磁盘上的文件已经修改,但是缓存获取到的信息判断文件没有修改,所以不会重新读取资源内容,这样就导致浏览器没有及时获取最新的资源。默认失效时间为5秒比较合理,不宜将该时间调大。
注:篇幅有限,涉及到的Tomcat源码细节只展示了少部分,或者一句话带过,以后会对Tomcat的实现原理进行详细研究讨论。如有疑惑,请留言!
Tomcat源码详细注释链接(非推广,持续更新):https://gitee.com/stefanpy/tomcat-source-code-learning
以上是关于Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)的主要内容,如果未能解决你的问题,请参考以下文章