为什么浏览器会在收到非200响应时重新请求脚本?

8
请将以下HTML保存为本地文件,例如/tmp/foo.html,然后在Firefox中打开(我使用的是49.0.2版本)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="http://localhost:1234/a.js"></script>
<script src="http://localhost:1234/b.js"></script>
<script src="http://localhost:1234/c.js"></script>
<script src="http://localhost:1234/d.js"></script>
<script src="http://localhost:1234/e.js"></script>
</body>
</html>

我没有在1234端口上运行服务器,所以请求甚至无法成功连接。
我期望的行为是所有请求都失败,并且完成它。
实际上,在Firefox中发生的是所有5个.js文件并行请求,它们无法连接,然后最后4个按顺序重新请求。就像这样:

enter image description here

为什么?

如果我在1234上启动一个总是404的服务器,行为是相同的。

这个特定的例子在Chrome中没有重现相同的行为,但其他类似的例子是我最初发现这种行为的方式。

编辑:这是我测试它在404时发生的方式。

$ cd /tmp
$ mkdir empty
$ cd empty
$ python -m SimpleHTTPServer 1234

然后重新加载了Firefox。它显示如下:

![enter image description here

服务器实际上也看到了所有这些请求(前5个请求由于并行请求而无序,但最后4个请求始终是b、c、d、e,因为它们在串行中重新请求)。

127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /d.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /c.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /b.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /a.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /e.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /b.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /c.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /d.js HTTP/1.1" 404 -
127.0.0.1 - - [02/Nov/2016 13:25:40] code 404, message File not found
127.0.0.1 - - [02/Nov/2016 13:25:40] "GET /e.js HTTP/1.1" 404 -

它试图确定您是否真的没有在端口1234上运行服务器,或者是否存在间歇性网络问题。它确实无法神奇地知道是否真的没有服务器在运行。 - slebetman
即使有一个响应404或500的服务器,它仍会执行该操作。 - Jamie Wong
我已经在Firefox、Chrome、Edge和IE中尝试过这个问题——如果服务器响应404,这些浏览器中没有一个会尝试超过一次,所以那个评论是错误的。 - Jaromanda X
@JaromandaX 请提供证明截图和重现指令,以便查看404错误。 - Jamie Wong
1个回答

10

这与并行资源加载可能出现的边缘情况有关,其中JavaScript被期望阻止其他资源的加载。

当你将延迟添加到错误响应中时,这种行为会变得更加清晰。以下是在每个请求中添加了1秒延迟的Firefox网络面板截图。

network panel

正如我们所看到的,所有5个脚本都是并行请求的,如现代浏览器所做的,以减少加载时间。

但是,除了第一个脚本外,返回404的那些脚本被重新请求,而不是并行请求,这几乎肯定是为了保持与一些旧版浏览器行为的向后兼容性。

历史上,浏览器一次只会加载和执行一个脚本。现代浏览器将它们并行加载,同时仍然保持执行顺序。

那么这为什么重要呢?

想象一下,如果第一个脚本请求改变了应用程序状态,例如设置一个cookie或其他东西来验证进一步的请求。在新的并行加载中,这些脚本将在更改状态之前被请求,假设Web应用程序设计得足够好,会抛出错误。

因此,确保其他资源在脚本没有机会在它们被请求之前更改状态而导致错误的唯一方法是重新请求资源。

事实上,这种重新请求的行为不仅限于脚本,还可以看到影响在并行加载的脚本标签之后出现错误的图像。

network panel 2

由于这些图像可能因为先前的脚本未先执行而无法加载,所以它们都会被并行重新请求。

有趣的是,我在规范中没有找到与此直接相关的内容,但来自Living Standard的这一部分表明,这种行为实际上可能违反了规范。

对于经典脚本而言,如果存在async属性,则经典脚本将与解析同时并行获取并在其可用时立即评估(可能在解析完成之前)。如果不存在async属性但存在defer属性,则经典脚本将并行获取并在页面解析完成后进行评估。如果两个属性都不存在,则会立即获取和评估脚本,并阻塞解析直到它们都完成。

如果解析实际上被阻止,那么似乎以下脚本标签和图像就不应该被读取以进行加载。我怀疑浏览器通过在执行之后才将以下标签变为DOM可用来解决这个问题。

注意:

在这些情况下,您将看到的确切行为可能会有所不同。只有在与脚本并行请求的资源才会重新加载。如果图像之后发生错误,但在加载脚本时没有请求它,则无需重新请求。另外,Chrome仅在潜在更改状态的脚本未发生错误时才触发此行为,但Firefox即使出现错误也会触发此行为。


这正是我想到的,但我很惊讶每个我尝试过的浏览器都有某种这种行为的变化,但不是一致的。这实际上是在规范中吗,还是只是浏览器为了向后兼容而达成的一些共识? - Jamie Wong
@JamieWong,我在规范中没有找到关于此事的任何信息,只有一节似乎表明规范不允许这样做。我怀疑一个供应商开始这样做,其他人便效仿并制定了自己的规则。 - Alexander O'Mara
最后一句话很有趣——你的意思是说,只有在至少一个脚本成功的情况下,Chrome才会触发,此时它将重新触发最初并行请求的所有随后失败的请求? - Jamie Wong
@JamieWong 是的,或者更具体地说,如果脚本成功,则会重试在脚本加载时启动的失败请求。 - Alexander O'Mara
关于“解析被阻止”的问题,可能并不是显式违反规范,因为这些请求可能不是来自解析器,而是来自预加载扫描器,因此您永远不会构建无效的解析树,但仍会发送请求。即使在并行请求时,脚本仍然按顺序执行(因此,即使脚本2在脚本1之前下载完成,脚本2的执行也会在脚本1的执行上阻塞)。 - Jamie Wong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接