Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)

Posted 徐同学呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)相关的知识,希望对你有一定的参考价值。

一、前言

Tomcat根据Servlet规范实现了Servlet容器,同时也具备HTTP服务器的功能。而Servlet在Tomcat中可以分为三种:普通ServletJspServletDefaultServlet。普通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的一段源码:

org.apache.catalina.servlets.DefaultServlet#serveResource

当判断获取的资源是文件时,会去判断请求的headerscheckIfHeaders()):

org.apache.catalina.servlets.DefaultServlet#checkIfHeaders

有四种判断headers的方法,因为默认Request Headers中有If-Modified-SinceIf-None-Match,所以暂时只看checkIfNoneMatch

304

首先判断request的headers中是否有If-None-Match,有则判断headerValueeTag是否相等,相等则说明文件没有修改,返回状态码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读取文件内容,同时将EtagLast-Modified设置到response里返回给浏览器,下次请求浏览器在请求头里回传If-Modified-SinceIf-None-Match(内容上If-Modified-Since对应Last-ModifiedIf-None-Match对应Etag),DefaultServlet响应时判断这两个参数和资源文件当前的值是否相等。

Tomcat利用浏览器缓存响应静态资源

2、Tomcat对静态资源加缓存

Tomcat对静态资源做了一层封装WebResource(接口),WebResource对文件基础信息的获取进行了包装再调用,同时对io操作进行了封装,方便直接获取文件内容。Tomcat对WebResource做了一层缓存(包括lastModifiedcontentLengthcanReadisFile等基础信息以及文件内容),默认缓存失效时长为5秒。获取WebResource时先尝试获取CachedResourceCachedResource为空或者已经失效则获取FileResourceFileResource存在则加缓存CachedResource,一个常规的缓存获取和更新操作。(CachedResourceFileResource都实现了接口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+浏览器缓存)的主要内容,如果未能解决你的问题,请参考以下文章

什么是Tomcat响应静态资源?

什么是Tomcat响应静态资源?

Tomcat&Servlet快速入门

Tomcat&Servlet快速入门

搞懂Tomcat

tomcat