从输入URL到页面可交互的过程探究之一从服务端到客户端

26次阅读

共计 3732 个字符,预计需要花费 10 分钟才能阅读完成。

原文:https://alistapart.com/articl…

最近发现国外有一个系列,专门探究从输入 URL 到页面可交互的详细过程,是一份干货十足的好资料。笔者决定分为四篇文章对其进行 有删减 地翻译,只希望能对大家有所帮助,毕竟这是前端必备的知识点,也是容易忽略掉某些细节的知识点。事先声明,这个系列完全由笔者手翻,如有翻译不当的地方,恳请读者给出改进意见!

接下来开始第一篇——从服务端到客户端

在浏览器执行任何工作之前,它需要先知道访问的是哪里。有几种方法可以实现访问:在地址栏中输入 URL、点击 (或触碰) 一个页面上或其他 app 中的超链接、或者点击你的收藏。无论是哪种情况,都会触发一个动作——导航。导航永远是网页中交互的第一步,因为它触发了如下一系列事件的连锁反应直至网页被加载。

初始化请求

一旦 URL 被提供给浏览器去加载,以下这些事情就会悄悄在背后发生:

检查 HSTS

首先,浏览器需要判断这个 URL 是否明确为 HTTP(不安全)协议。如果它是一个 HTTP 的请求,那么浏览器则需要检查这个域名是否在 HSTS 的清单中(HTTP Strict Transport Security——严格安全传输)。这个清单包含了一个预加载好的名单以及你之前访问过的使用 HSTS 的网站名单,它们都是存放在浏览器中的。如果你请求的 HTTP 开头的 host 处于在 HSTS 清单中,那这个请求会被强制转为 HTTPS 开头的 URL 而非 HTTP。这就是为什么你会发现当你试图在一个现代浏览器中输入 http://www.bing.com 会被转为 https://www.bing.com。

检查 SERVICE WORKERS

接着,浏览器需要判断 service worker 是否可以用来处理请求——这对于那些离线的没有网络连接的用户来说至关重要。Service workers 相对来讲是比较新的浏览器特性。它通过对网络请求的拦截来提供离线应用的能力,这些请求都可以被保留在脚本控制的缓存中。这是很有用的,因为它使网站能够更好地控制何时使用缓存的项目。这些缓存是跟域名绑定的,这意味着每个域都可以有自己的缓存黑盒,并与其他域的缓存隔离开。

当一个页面被访问时,可以注册一个 Service worker,这个动作是由一个工作线程来完成的,它可以把 service worker 的注册和 URL 映射记录在本地数据库中。要判断一个 service worker 是否被安装,只需在这个本地数据中查找是否有对应的 URL。如果为 service worker 查到了对应的 URL,它就会被允许处理请求的回应。而如果浏览器支持 Navigation Preload 的新特性,且开发者使用了它,那么浏览器会同时去发起首次导航请求。这是有好处的,因为它避免了浏览器因为 service worker 启动过慢而对页面渲染的影响。

当浏览器发现没有 service worker 来处理初始化请求时,就会继续网络请求层。

检查网络缓存

浏览器会通过网络请求层检查缓存中是否存在全新的响应。这经常是由响应头中的 Cache-Control 字段决定的,字段中设置的 max-age 值可以决定缓存多久会刷新,而 no-store 字段可以表明是否应该被缓存。可想而知,如果浏览器在缓存中找不到任何东西,那么就需要进行网络请求了。而如果在缓存中有一个全新的响应,它就会被立即返回以用于页面加载。如果存在一个不够“新”的资源,那么浏览器会把这个请求转为一个附带条件的校验请求,也就是请求头带上 If-Modified-Since 或者 If-None-Match 去告诉服务端当前浏览器存的是哪个版本的缓存。服务端则可以返回 HTTP 304 状态码(没有更改) 告诉浏览器这个缓存是最新的,不带响应正文;或者返回 HTTP 200 状态码告诉浏览器这个缓存资源已经过期了,并直接返回最新的资源

检查网络连接

如果现在有一个和主机、端口建立起连接的请求,那么它会被浏览器复用而不是重新去建立一个,否则,浏览器会走网络层以了解是否需要执行 DNS(域名系统)查询。这个动作的具体流程是,先寻找本地的 DNS 缓存(存储在你的设备上),然后根据 DNS 缓存是否过期来决定是否访问远程域名服务器(它们由互联网服务提供商 ISP 分配主机地址),域名服务器最终会返回准确的 IP 地址给浏览器进行连接。

某些情况下,浏览器能够预先知道哪些域名会被访问,从而先准备好对这些域名的连接。一个网页可以通过在 link 标签中使用资源提示(resource hints),比如rel=”preconnect” 来提示浏览器提前准备好连接。在如下场景中,资源提示是很有用的,比如一个用户在必应的搜索结果页,而通常的预期中,前几条搜索结果是最有可能被用户访问的。此时,提前准备好对那些域名的连接可以在那些网页被点击之后节省掉 DNS 查询和域名连接的消耗。

建立起连接

浏览器现在可以与服务器建立起连接了,且服务端知道自己需要从客户端接收和发送消息了。如果我们是使用 TLS,我们需要执行一次 TLS 握手流程以验证服务器提供的证书。

发送请求给服务器

第一个通过这个连接发起的请求叫做顶级页面请求。通常情况,这个请求的资源会是一个 HTLML 文件,从服务器返回到客户端

处理响应

当响应以数据流的形式到达客户端后,客户端就开始进行解析了。首先,浏览器会检查响应头。HTTP 头部是以键值对的形式作为 HTTP 响应的一部分。如果响应头指示要进行重定向(比如,通过 Location 字段),浏览器就会再一次进行导航并回到最初的那一步,检查是否需要执行 HSTS 的升级(为 HTTPS)。

如果服务器的响应数据被压缩或分块了,浏览器会尝试对它进行解压和合并。

待响应被解读完成后,浏览器还会并行地将其写入网络缓存中。

接着,浏览器会搞清发送过来的文件的 MIME 类型,这样它才能以适当的方式去加载这份文件。比如,一份图片文件会原封不动地被加载进来,但 HTML 文件则会被执行解析和渲染。如果 HTML 解析器被调用了,那么它会扫描出那些可能要下载的资源文件的 URL,以便浏览器在页面渲染之前就可以开始去下载。这一部分的更多细节会在系列文章的下一篇中具体展开。

截至目前,被请求的导航 URL 已经输入到了浏览器的历史中,这样它就可以被用于浏览器导航的前进和后退功能了。

这里有一张更详细的流程图,它可以让你对目前讨论的内容有个总体的概览:

如你所知的,页面会继续发起请求,因为页面上还有很多对整体体验很重要的子资源,比如图片,脚本,和样式表。另外,这些子资源中引用到的其他资源,比如背景图片 (CSS 中引用的),或者其他由fetch()import()AJAX 请求发起的资源。如果没有这些的话,我们将只能看到一个原始的无交互的空页面。

再谈缓存

刚刚已经提到,浏览器会管理网络缓存,以便在多种场景下能对下载好的资源的重复利用。这对那些长久不更新的资源尤其有用,比如 logo 和第三方的脚本文件。我们应该尽可能地利用好这些缓存,因为这有利于减少对外的网络请求数,取而代之的是本地的可复用的缓存资源。

响应头中的 Cache-Control 字段控制着浏览器的缓存逻辑。某些情况下,你可以谨慎地告诉浏览器完全不要进行缓存,比如使用 Cache-Control: no-store,因为这个资源在预期中是一直在变化的。另一种情况下,当给定 URL 的响应内容永远不会变化时,我们可以设置Cache-Control: immutable 以便浏览器可以永远地缓存它。实际应用中,当我们使用不同的 URL 来指向不同版本的同一份资源时,我们就可以采用这种做法,而非对同一个 URL 的资源进行更改,因为被缓存的版本会一直被使用且不会去发送请求。

Origin 模型

Origin 是由协议,主机名和端口共同组成的。例如,https://www.bing.com:443 这个 origin 是由 https 的协议,www.bing.com 的主机名和 443 的端口组成的。只要其中任何一个部分有差异,那么在两者进行比较时,都会被认为是不同源的。比如 https://images.bing.com:443 和 http://www.bing.com:80 就是不同源的。

Origin 对于浏览器来说是很重要的概念,因为它定义了数据是如何被隔离和保护的。大多数情况,为了安全考虑,浏览器会强制使用同源策略,意味着一个源无法访问另一个源的数据。就像上面提到的两个源——https://images.bing.com:443 和 http://www.bing.com:80,它们互相都无法访问对方的缓存(service worker 的)。

如果 bing.com 想要从 microsoft.com 加载一个 Javascript 文件,它就需要在实行同源策略的浏览中发起一个跨域资源请求。想要允许这种操作的话,microsoft.com 就需要与 bing.com 通过指定 CORS(跨域资源共享)的头部进行合作。

总结

既然你已经明白了资源如何从服务器走到客户端以及之间的所有细节,那么请继续关注网页加载的下一步:从 HTML 标签转为 DOM。

ps:欢迎关注微信公众号——前端漫游指南,刚刚开设不久,会定期发布优质原创文章和译文,感谢~

正文完
 0