HTTP协议中约定客户端和服务端通过请求首部 If-None-Match
、 If-Modified-Since
、Cache-Control
和响应首部 Expires
、Cache-Control
、Etag
、Last-Modified
等协调处理客户端缓存机制。那么Apache作为Web服务器是如何控制以上响应首部字段的行为的呢?
Expires 首部
控制 Expires 首部的行为的是 expires_module
模块。
ExpiresActive
描述: 允许生成响应首部 Expires
语法: ExpiresActive On|Off
语境: 系统配置文件, <VirtualHost>
片段, <Directory>
片段,.htaccess
默认: ExpiresActive Off
覆盖: Indexes
状态: Extension
模块: mod_expires
设置为Off一定不会生成,设置为On,还要依赖指令 ExpiresByType 和 ExpiresDefault 才能生成响应首部 Expires
。
ExpiresByType
描述: 为不同 Mime-Type 配置不同的 Expires
语法: ExpiresByType MIME-type <code>seconds
语境: 系统配置文件, <VirtualHost>
片段, <Directory>
片段,.htaccess
覆盖: Indexes
状态: Extension
模块: mod_expires
这个指令为不同 Mime-Type 的文档定义 Expires
头部的值。同时还会通过从过期日期中减去请求时间并以秒来表示的结果来定义响应首部 Cache-Control
的 max-age
指令的值。
参数 code
表示基本时间: 用M
表示应该使用文件的最后修改时间作为基本时间,A
表示应该使用客户端的访问时间。第二个参数表示基本时间之后的秒数,以此来构造到期时间。例如:
ExpiresByType image/gif A2592000
ExpiresByType text/html M604800
还可以使用备用语法 base[plus num type] [num type] … 指定到期时间的计算。
base 可用的值有:
- access 表示客户端访问时间
- now 等同于access,表示客户端访问时间
- modification 文档最后修改时间
plus是可选的,num必须是整数值,type可用的值有:
- years
- months
- weeks
- days
- hours
- minutes
- seconds
可用通过使用多个 [num type] 来使到期时间更精确:
ExpiresByType text/html "access plus 1 month 15 days 2 hours"
ExpiresByType image/gif "modification plus 5 hours 3 minutes"
ExpiresDefault
描述: 计算到期时间的默认算法
语法: ExpiresDefault <code>seconds
语境: 系统配置文件, <VirtualHost>
片段, <Directory>
片段,.htaccess
默认: ExpiresDefault Off
覆盖: Indexes
状态: Extension
模块: mod_expires
此指令用于计算所有文档的到期时间的默认算法。它可以被 ExpiresByType
指令逐个类型地重写,未被重写的文档类型使用默认算法。
注意: ExpiresDefault
指令对动态网页同样有效。目前已知的PHP请求中如果使用 session_start
开启了会话,请求则会返回一个过去的时间作为过期日期,同时将 Cache-Control
设置为 no-store,no-cache,must-revalidate
。
到期时间的表示方法与指令 ExpiresByType 一致。
Cache-Control首部
在设置 Expires
首部的同时,Apache服务器会定义响应首部 Cache-Control
的 max-age
指令的值为过期日期减去请求时间的秒数。
另外,mod_headers
模块可以直接设置响应首部,也可以用来设置 Cache-Control
#启用headers_module模块
LoadModule headers_module modules/mod_headers.so
Header set Cache-Control "max-age=1000"
可以结合 <Directory>、<Location>、<File>等配置片段,对不同类型的文档使用不同的 Header
指令。
Header
描述: 配置HTTP响应首部
语法: Header [condition] add|append|echo|edit|edit*|merge|set|setifempty|unset|note header [[expr=]value [replacement] [early|env=[!]varname|expr=expression]]
语境: 系统配置文件, <VirtualHost>
片段, <Directory>
片段,.htaccess
覆盖: FileInfo
状态: Extension
模块: mod_headers
该指令可以替换,合并或删除HTTP响应头。
可选的condition参数用来确定此指令将针对哪个响应头进行操作:
- onsuccess,成功时,默认值,可以省略
- always 总是。设为always时即使发生内部重定向也会保留设置的响应头。
Header指令可以对响应首部执行的操作有:
- add 即使此响应首部已存在,也会添加到现有报文首部中。这可能导致两个(或更多)具有相同名称的响应首部。
- append 响应首部将附加到同名的首部。将新值合并到现有首部时,它将使用逗号与现有首部值分隔。
- echo 具有此名称的请求首部将在响应首部中回显。
- edit* 如果此响应头存在,则根据正则表达式搜索和替换其值。
- merge 响应首部将附加到同名的首部,除非要附加的值已经出现在首部的逗号分隔列表中。
- set 设置响应首部,用此名称替换任何先前的首部。
- setifempty 仅当没有此名称的响应首部时,才设置响应首部
- unset 如果存在此名称的响应首部,则将其删除。如果有多个相同名称的首部,则将删除所有。
Last-Modified
Last-Modified
是文档的最后修改时间,没有指令来控制此响应首部,对于动态文档,Apache服务器使用当前时间作为 Last-Modified
Etag
Etag
和 Last-Modified
起到的作用相同,都是为了判断文件是否更新,但是 Last-Modified
只能精确到秒,如果在一秒内发生多次更新,Last-Modified
就无能为力了,这种场景 Etag
仍然是可靠的。
FileEtag
描述: 使用文件属性为静态文件创建响应报文中的ETag
语法: FileETag component …
语境: 系统配置文件, <VirtualHost>
片段, <Directory>
片段,.htaccess
默认: FileETag MTime Size
覆盖: FileInfo
状态: Core
模块: Core
允许设置值有:
- INode: 文件的节点编号
- MTime: 文件的最后修改时间
- Size: 文件的字节数
- All: 以上三个值
- None: 不返回Etag响应首部
INode、MTime和Size可以使用空格分割,设置多个
FileETag INode MTime Size
设置值时可以在关键字前使用 +
或 -
来改变从其他地方继承来的配置,没有使用前缀则表示全新设置,完全忽略继承的值。
注意: Header unset Etag
指令可以将 Etag 从响应报文中移除
不能缓存的例外情况
gzip
在 Apache2.4
版本(据说 Apache2.5
已修复),开启了 gzip
压缩后, 会自动给 Etag
追加 -gzip
字符,导致客户端请求时提交的 Etag
是含有 -gzip
的,而 Apache
对比的是不含 -gzip
的,从而导致 Apache
始终认为两者不一致,资源发生了变更,也就是始终返回 200
响应,最终导致缓存失效。
解决办法:在配置中加入以下代码
<FilesMatch "\.(js|css|html|htm|png|swf|pdf|shtml|xml|flv|gif|ico|jpeg)$">
RequestHeader edit "If-None-Match" "^(.*)-gzip(.*)$" "$1$2"
Header edit "ETag" "^(.*)-gzip(.*)$" "$1$2"
</FilesMatch>
本地 HTTPS 证书 无效,Chrome不缓存,火狐和IE等正常缓存
参考实例
#资源文件长时间缓存,有更新时通过url后跟版本解决缓存问题
ExpiresActive On
ExpiresByType image/* "access plus 5 years"
ExpiresByType text/css "access plus 5 years"
ExpiresByType application/javascript "access plus 5 years"
#text/html 类型包括 PHP 等动态网页请求,这里通过 FileMatch 只给 html 后缀开启缓存
#通过 no-cache 要求不管缓存是否过期均应向服务器检查,以此来解决缓存的更新问题(资源文件通过版本控制缓存的更新问题)
<FilesMatch "\.html$">
ExpiresByType text/html "access plus 5 years"
Header append Cache-Control: public,no-cache
</FilesMatch>
#开启gzip后导致缓存生效
<FilesMatch "\.(js|css|html|htm|png|swf|pdf|shtml|xml|flv|gif|ico|jpeg)$">
RequestHeader edit "If-None-Match" "^(.*)-gzip(.*)$" "$1$2"
Header edit "ETag" "^(.*)-gzip(.*)$" "$1$2"
</FilesMatch>