Tomcat中对静态资源的处理教程
前⾔
Tomcat 中的请求都是由 Servlet 处理,静态资源也不例外。在默认的 l 中,配置了⼀个 DefaultServlet ⽤于处理静态资源,它⽀持缓存和断点续传。
DefaultServlet 的基本处理过程如下:
查资源是否存在缓存
检查是否满⾜可选 If 头域指定的条件
设置响应头域,如 Content-Type、Content-Length、ETag、Last-Modified
检查是否满⾜ Sendfile 的条件,否则将内容拷贝到输出流中
接下来主要分析资源缓存的设计和实现,以及 If 头域的处理。
1. 资源缓存的设计
访问磁盘的速度远远低于访问内存的速度,所以适当的缓存⼀部分静态资源能够让系统快速响应。
Tomcat 在 6.0.53 版本实现静态资源的处理时,借助了 JNDI 的⼀些 API(但在使⽤时感觉与 JNDI 的关系不⼤),相关类图及核⼼⽅法和属性如下:
缓存相关的类:
ResourceCache: 缓存实现,提供了资源查、加载、销毁的功能
CacheEntry: ⼀个缓存条⽬,包含缓存名称,如 /tomcat.gif,资源和资源的属性以及对应的⽬录
资源⽬录相关的类是:
EmptyDirContext: 主要⽤于嵌⼊式模式,⾏为就像没有可⽤资源⼀样
servlet和tomcat的关系FileDirContext: 基于⽂件系统的资源⽬录服务
WARDirContext: 基于 war ⽂件的⽬录服务
Resource: 封装了资源内容,主要有字节数据和输⼊流
ResourceAttributes: 资源属性,主要有内容长度和最后修改时间
ProxyDirContext: 资源缓存和⽬录服务的代理,提供查资源缓存、校验缓存是否过期等功能
默认情况下,缓存最⼤为 10 MB,单个缓存资源最⼤为 512 KB,缓存的 TTL 为 5s。
⼀般的,在 Mapper 映射到处理静态资源的 Wrapper 时,会引起资源的加载,基本的⽅法调⽤情况如下:
Mapper.map(MessageBytes, MessageBytes, MappingData)
└─Mapper.internalMap(CharChunk, CharChunk, MappingData)
└─Mapper.internalMapWrapper(Mapper$Context, CharChunk, MappingData)
└─ProxyDirContext.lookup(String)
└─ProxyDirContext.cacheLookup(String)
└─ResourceCache.lookup(String)
└─ResourceCache.find(CacheEntry[], String)
缓存资源插⼊内部数组时是有序的,find ⽅法就是通过资源名⼆分查缓存,资源名就是请求路径,此时有两种情况,缓存命中和未命中。
缓存未命中,在 cacheLookup ⽅法中会新建⼀个 CacheEntry 对象,调⽤ cacheLoad ⽅法加⼊到 ResourceCache 的缓存数组中,加⼊前会对缓存条⽬进⾏以下操作:
获取并初始化缓存资源属性,主要是⽂件的 contentLength 和 lastModified
如果⽂件长度⼩于 512KB,那么将⽂件内容加载到内存中
标记缓存存在,设置缓存时间戳
缓存命中,会对缓存条⽬进⾏校验:
检查是否过期,当前时间⼤于缓存条⽬设置的时间戳
如果过期,再检查资源内容是否修改
如果修改,清除这个缓存,读取最新内容
以上就是资源缓存简单的处理过程。
2. If 头域的处理
客户端接收并缓存请求的资源,,当再次请求此资源时,服务端根据特定的请求头域来验证资源是否修改,没有变动,则只返回⼀个 304 Not Modified 响应,否则返回资源的内容,从⽽节省带宽。
⽤于资源验证的头域有两种,分别是:Last-Modified+If-Modified-Since 和 ETag+If-None-Match。
Last-Modified+If-Modified-Since,单位是秒,这个容易理解,如果服务端资源的最后修改时间⼩于 If-Modified-Since 的值,表⽰资源⽆变动。与 If-Modified-Since 对应的有个 If-Unmodified-Since,它类似⼀个断⾔,⼩于此时间戳的资源才返回,⼤于等于的话会返回 412 Precondition Failed 的错误。
使⽤时间戳校验有⼏个弊端:
⽂件有可能只改变修改时间,内容不变
⽂件在秒以下的时间修改⽆法判断
服务器可能不能精确获取⽂件的最后修改时间。
因此,HTTP 引⼊了 ETag。ETag(Entity Tags) 资源唯⼀标识,可看做服务端为资源⽣成的⼀个 Token,⽤于校验资源是否修改。HTTP 只规定 ETag 要放在双引号内,没有规定内容是什么或者要怎么实现,Tomcat ⽣成 ETag 的逻辑是 "W/\"" + contentLength + "-" + lastModified + "\"" ,其中 'W/' 表⽰⼤⼩写敏感。
ETag+If-None-Match,If-None-Match 的值由⼀个或多个 ETag 组成,多个以逗号分割,如果服务端资源的 ETag 与其中的任何⼀个都不匹配,表⽰请求的资源有修改;否则⽆变动。它还有⼀个特殊值-星号(*),只在资源上传时使⽤,通常是 PUT ⽅法,检查是否已经上传过。
此外 If-None-Match 的优先级⾼于 If-Modified-Since,也就是说,存在 If-None-Match 就不对最后修改时间进⾏校验。与 If-None-Match 相对的有个 If-Match,它也类似断⾔,只有资源的 ETag 匹配时才认为没有修改,通常⽤于断点续传。Tomcat 实现此部分的核⼼代码如下:
// 返回 true 是才认为资源有变动
protected boolean checkIfHeaders(HttpServletRequest request,
HttpServletResponse response,ResourceAttributes resourceAttributes)
throws IOException {
return checkIfMatch(request, response, resourceAttributes)
&& checkIfModifiedSince(request, response, resourceAttributes)
&& checkIfNoneMatch(request, response, resourceAttributes)
&& checkIfUnmodifiedSince(request, response, resourceAttributes);
}
2.1 ⼀次请求流程
以请求 /main.css 静态资源为例,第⼀次请求响应头信息如下:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"72259-1557127244000"
Last-Modified: Mon, 06 May 2019 07:20:44 GMT
Content-Type: text/css
Content-Length: 72259
Date: Mon, 06 May 2019 07:20:57 GMT
第⼆次请求时,⾸先看⼀下请求头域关键信息:
Cache-Control:max-age=0
Connection:keep-alive
Host:localhost:8080
If-Modified-Since:Mon, 06 May 2019 07:20:44 GMT
If-None-Match:W/"72259-1557127244000"
服务器收到请求后就会⽐对 ETag,这⾥匹配成功,表⽰资源没有修改,响应如下:
HTTP/1.1 304 Not Modified
Server: Apache-Coyote/1.1
ETag: W/"72259-1557127244000"
Date: Mon, 06 May 2019 07:21:46 GMT
注意:在复现时,要使⽤⽂本类型,如果使⽤ Chrome 浏览器,记得开启缓存。
2.2 Accept-Ranges
在上⽂的响应中,服务器设置了⼀个 Accept-Ranges: bytes 头,字⾯理解就是可以请求资源的⼀部分字节,客户端发现有这个头时,就可以尝试断点续传。
解析过程就是对 HTTP 规范的实现,这⾥不在具体分析了,规范详细信息可查看 RFC7233#section-2.3.
3. SendFile 的处理
检查是否⽀持 SendFile,NIO 模式下⽀持此操作,也就是零拷贝,此操作会减少⼀次到应⽤内存的拷贝,直接从内核将数据写⼊通道。Tomcat 在⽂件⼤⼩⼤于 48KB 时会尝试使⽤此⽅式发送。
4. ⼩结
Tomcat 对静态资源处理的实现还是⽐较完善的,但还是略逊⾊于 Nginx 这类 Web 服务器,因为它们
能直接处理静态资源,⽽ Tomcat 还要多做⼀次映射。⼀般的都会进⾏动静分离,让 Tomcat 专注处理动态请求。
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。