Chrome或Firefox浏览器缓存时间问题
1、浏览器默认缓存时间
firefox 的缓存时间时长
(访问时间 - 最后修改时间) ÷ 10
例子:
假设 7点0分 访问的 5点0分修改的 index.html ,
那么缓存时间为
2*60*60 ÷ 10 = 720 秒
页面缓存时间为 720 秒
2、设置页面禁止缓存
-
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
-
<meta http-equiv="Pragma" content="no-cache" />
-
<meta http-equiv="Expires" content="0" />
no-cache : 协商缓存
no-store : 不缓存
must-revalidate : 过期强制请求服务器
先放上结论吧,Chrome和Firefox对js、css之类的文件,在内存中的缓存时长,是:
(访问时间 - 该文件的最后修改时间) ÷ 10
假设文件 a.js 最后编辑时间是 2018年12月1号 10点0分0秒;
Chrome的第一次访问时间是 2018年12月1号 12点0分0秒;
第一次访问与文件编辑时间相差2小时,即7200秒,那么缓存时长就是720秒
即结论如下:
1、在 2018年12月1号 12点0分1秒到 12点11分59秒,这12分钟内,浏览器不会发起http请求;
2、在 2018年12月1号 12点12分0秒,会发起带 If-Modified-Since 的http请求
3、如果希望浏览器每次都发起http请求,请在WebServer返回Header Cache-Control: no-cache
问题的由来:
我提供了一个多语言的js资源包服务,昨天有QA反馈,后台修改了内容,前台没变化!!
PC上还好办,可以按Ctrl + F5,强制刷新,手机上就不好办了,只能等着缓存过期。
而且我们也不可能主动通知用户去强制刷新吧!
问题解决很简单,在IIS的站点=》HTTP响应标头里,添加一个Header:Cache-Control,值为no-cache,
问题解决。
注意:加了这个标头后,浏览器在请求这个站点的js/css/图片资源时,每次都会重新发起HTTP连接请求,虽然请求的Header里会带上 If-Modified-Since,但是HTTP连接本身也是很耗资源的,所以要根据场景来选择性添加,
比如不添加标头,而是通过js加版本号来避免缓存。
虽然缓存问题解决了,但是如果没加标头,Chrome会在内存缓存多久啊?这个问题我搜索了一下,没有找到Chrome的资料,但是有文章说Firefox是按顶部的结论实现的,参考RFC协议关于缓存过期的部分:
https://tools.ietf.org/html/rfc2616#section-13.2.4
为了验证,写了一个html定时刷新自己,然后扔在IIS站点下,然后用Chrome的F12->Network抓包:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="jq_125bece.js"></script>
<script type="text/javascript" src="errnew.js"></script>
<link rel="stylesheet" href="a.css"/>
<script type="text/javascript">
$(document).ready(function() {
setTimeout(function(){
location.href = 'a.html?' + Date.now();
}, 10000);
});
</script>
</head>
<body>
</body>
</html>
验证的结果,Chrome也是按这个机制作为本地缓存过期策略。
文章最后,贴一段C#版本的判断304响应的代码,用于客户端更新本地资源:
static void Main(string[] args)
{
var url = "https://beinet.cn/language.js";
var localFile = @"d:\language.js";
var ret = UpdateResource(url, localFile);
Console.WriteLine("是否有更新" + ret);
ret = UpdateResource(url, localFile);
Console.WriteLine("是否有更新" + ret);
Console.Read();
}
/// <summary>
/// 更新本地资源文件,并返回是否进行了更新
/// </summary>
/// <param name="url">远程资源文件地址</param>
/// <param name="localFile">本地缓存资源地址</param>
/// <returns></returns>
static bool UpdateResource(string url, string localFile)
{
const string responseHeader = "Last-Modified";
var timeFile = localFile + responseHeader;
string lastModified = null;
if (File.Exists(timeFile))
{
lastModified = File.ReadAllText(timeFile);
}
var request = (HttpWebRequest)WebRequest.Create(url);
request.Headers.Add("Accept-Encoding", "gzip, deflate");
request.Timeout = 5000; // 默认5秒超时
request.AllowAutoRedirect = true;
if (!string.IsNullOrEmpty(lastModified))
{
request.IfModifiedSince = DateTime.Parse(lastModified); // 设置Header if-modified-since
}
string json;
HttpWebResponse response;
try
{
response = (HttpWebResponse) request.GetResponse();
}
catch (WebException exp)
{
if(exp.Response != null && ((HttpWebResponse)exp.Response).StatusCode == HttpStatusCode.NotModified)
return false;
throw;
}
using (response)
{
lastModified = response.Headers[responseHeader];
json = GetResponseString(response, Encoding.UTF8);
}
// todo: 这里要考虑判断是否json格式
SaveToFile(localFile, json);
SaveToFile(timeFile, lastModified);
return true;
}
/// <summary>
/// 从HttpResposne中获取响应字符串
/// </summary>
/// <param name="response"></param>
/// <param name="encoding"></param>
/// <returns></returns>
static string GetResponseString(HttpWebResponse response, Encoding encoding)
{
using (Stream stream = response.GetResponseStream())
{
if (stream == null)
{
return "GetResponseStream is null";
}
string str;
string contentEncoding = response.ContentEncoding.ToLower();
if (contentEncoding.Contains("gzip"))
{
using (Stream stream2 = new GZipStream(stream, CompressionMode.Decompress))
{
str = GetFromStream(stream2, encoding);
}
}
else if (contentEncoding.Contains("deflate"))
{
using (Stream stream2 = new DeflateStream(stream, CompressionMode.Decompress))
{
str = GetFromStream(stream2, encoding);
}
}
else
{
str = GetFromStream(stream, encoding);
}
return str;
}
}
static string GetFromStream(Stream stream, Encoding encoding)
{
using (StreamReader reader = new StreamReader(stream, encoding))
{
return reader.ReadToEnd();
}
}
static void SaveToFile(string targetFile, string content)
{
var now = DateTime.Now.ToString("yyyyMMddHHmmssfffffff");
// 写入临时文件,再进行移动
var tmpFile = targetFile + now;
File.WriteAllText(tmpFile, content);
if (File.Exists(targetFile))
{
var bakFile = targetFile + "bak" + now; // 备份文件
File.Move(targetFile, bakFile);
}
File.Move(tmpFile, targetFile);
}