欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

Tomcat中对静态资源的处理教程

程序员文章站 2022-04-06 11:12:25
前言 tomcat 中的请求都是由 servlet 处理,静态资源也不例外。在默认的 web.xml 中,配置了一个 defaultservlet 用于处理静态资源,它支...

前言

tomcat 中的请求都是由 servlet 处理,静态资源也不例外。在默认的 web.xml 中,配置了一个 defaultservlet 用于处理静态资源,它支持缓存和断点续传。

defaultservlet 的基本处理过程如下:

  • 查找资源是否存在缓存
  • 检查是否满足可选 if 头域指定的条件
  • 设置响应头域,如 content-type、content-length、etag、last-modified
  • 检查是否满足 sendfile 的条件,否则将内容拷贝到输出流中

接下来主要分析资源缓存的设计和实现,以及 if 头域的处理。

1. 资源缓存的设计

访问磁盘的速度远远低于访问内存的速度,所以适当的缓存一部分静态资源能够让系统快速响应。

tomcat 在 6.0.53 版本实现静态资源的处理时,借助了 jndi 的一些 api(但在使用时感觉与 jndi 的关系不大),相关类图及核心方法和属性如下:

Tomcat中对静态资源的处理教程

缓存相关的类:

  • resourcecache: 缓存实现,提供了资源查找、加载、销毁的功能
  • cacheentry: 一个缓存条目,包含缓存名称,如 /tomcat.gif,资源和资源的属性以及对应的目录

资源目录相关的类是:

  • emptydircontext: 主要用于嵌入式模式,行为就像没有可用资源一样
  • 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 专注处理动态请求。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。