Mac OS 的Safari浏览器,刷新机制看起来与其他浏览器不同,似乎有个“预加载”的机制。比如说,我打开一个叫“test.jsp"的网页,这个网页是用java开发的。代码是下面这个样子:

<%
System.out.println("test.jsp out start")
if(session.getAttribut('name')==null){
    return;
}
%>
<!DOCTYPE HTML>
<html>
<script>
    $.get('/get',...)
</script>
</html>

这个jsp文件首先会在服务器端输入一句"test.jsp out start",表示服务器开始向客户端输出内容,然后判断session中有没有name这个变量,如果没有,则停止输出。如果有,则继续输出html内容

我们首先想办法让session中拥有name这个变量,然后请求test.jsp网页。

很显然,网页能够加载,并且在浏览器中自动发出一个XHR(ajax)请求,请求地址是“/get”。

/get地址对应的servlet代码如下:

System.out.println("用户请求了/get");
if(session.getAttribut("name")!=null){
    out.print("name is null");
}else{
    out.print("name is not null");
}
...

由于session中包含name变量,这个servlet会返回一个内容为“name is not null”的字符串

我们保持网页不动,然后想办法把session中的name变量去掉。然后重新刷新页面。

按照一般的逻辑,我们会认为,浏览器会重新请求test.jsp,然后在服务器端,判断session中不含有name变量,然后停止输入,因此后面的html代码,包括script脚本都不会输出到浏览器,因此刷新之后,XHR请求不会发出。

但是:

safari浏览器貌似有一个“预加载”的功能,当我们刷新test.jsp时,$.get('/get')触发了,而且触发在服务器判断session变量之前。在服务器控制台,我们会看到输出了这样几句话:

用户请求了/get
test.jsp out start

很懵逼,有木有?

是的,按照我们的逻辑,点击刷新之后,应该会先请求test.jsp,然后在服务器端,发现session中不存在name这个变量,就会停止后面内容的输出,因此浏览器端根本不会加载后面的任何一句html代码以及script代码。但是实际上,不仅浏览器执行了之前缓存的网页代码,发出了script中的XHR请求,而且顺序还排在了test.jsp网页执行的前面。

也就是说,点击刷新之后,Safari浏览器先重新加载了一遍之前的代码,把所有script脚本都执行了,然后(也可能是同时,也就是异步地)发出了指向test.jsp的请求。

更关键的地方来了,/get请求发出了,同时响应值还缓存在了Safari中,这个响应值是多少?

是:name is null

因为在刷新之后,预加载发出/get请求时,session中已经没有name这个变量了。

然后我们很快速地让session中又再次拥有了name这个变量,然后很快速地重新请求/get。

返回的响应值应该是多少?

按照我们的逻辑,由于session又有了name,应该是“name is not null”。如果我们断点测试服务器,会发现服务器执行的也是输出这句话。

然而,我们发现:

虽然服务器输出的的确是"name is not null",但是,safari中收到的响应值却是"name is null"

因为Safari帮我们缓存了刚才“预加载”时/get请求的返回值,虽然服务器做出了正确的响应,但是Safari并未采用,而是用了之前缓存的返回值。

现在,我们要做的就是让每次XHR请求都不使用缓存值,解决方案就是

$.get('/get?i='+new Date().getTime(),...)