输入url到页面展示过程发生了什么

133 阅读14分钟

2022寒冬……

以chrome为例,当我们键个第一个字符的时候,浏览器就已经开始工作了
我们在输入字符时,通常可以看到一些预期匹配项会在输入的下文展示,这是chrome的AutoComplete在工作的原因,这项技术在2010年10月之前被称为google suggest,AutoComplete会为我们自动匹配出一些可能的结果。
这时只讨论输入的是不包含非法字符url的情况,如果带有非法字符则还需要经历一轮非法字符校验,确认这是一个合法的url之后,还会进行一个url补全操作,浏览器会根据域名进行猜测,对我们们所输入的这段可能不完的url进行添加前、后缀来补全我们的url

按下回车

当我们按下回车键的那一刻起,浏览器还会执行一个beforeupload事件,用于清退当前页面内的工作内容,确请工作清除完毕后,会通过IPC将我们的URL发送给网络进程,这一步即是 构建请求 。网络进程会就本地缓存资源进行检查,如果我们本地已经缓存了这些资源,则会将此资源返回给浏览器进程进行解析。

查检缓存:会对强缓存进行检查,如果存在,则是会校验缓存是否过期,如果缓存已过期,进入到 解析URL步骤。
如果缓存没有过期,将直接使用。
缓存的检查过程:

  1. https:如果已经设置过请求拦截/离线缓存,则会向Service Worker进行查找
  2. 不满足1,会去Memory Cache查找
  3. 不满足2,会去Disk Cache查找
  4. 上述均不满足且是http2请求,会去Push Cache查找(session)

解析URL

众所周知,网络通信中,最终是靠IP地址来确定目的主机的,而我们所输入的URL其实只是域名,如果需要得到目标主机的IP。拿的到目标主机的IP之后,就可以正式发起请求了。

我们需要通过DNS ( domain name system )进行域名解析。

域名解析过程:
DNS会将域名和IP地址映射关系保存进一个分布式的数据库内

  1. 查询本地解析记录(递归查询)
    大致过程(全程客户端权需发出一次请求):

image.png

  1. 在客户端找不到解析的记录,会向设置的DNS服务器发起请求(迭代查询)
    1. 由本地的DNS服务器向ISP提供的DNS服务器发起请求,ISP的DNS服务器会查找对应的DNS缓存,如果有结果则返回,如无则进入到b
    2. 分两种情况
      1. 设置了转发器,会将该请求转发至上级服务器,若上级服务器无,则继续向上转发,不断循环
      2. 没有设置转发器,进入到c
    3. 本地域名服务器向13个根域名服务器去查询,根域名服务器会引导至下级服务(顶级域名服务器)查询
    4. 顶级域名服务器会引导至下级域名服务器(二级域名服务器) 根、顶级均不会返回映射关系
    5. 二级域名服务如果查找不到则是会继续向下引导

DNS优化策略

解析域名是需要时间的,在域名解析完成之前,浏览器无法从服务器获取任务信息,提供以下方案

  1. 减少DNS请求次数,减少主机名的数量
  2. dns-prefetch
  3. 减少查询的过程(负载均衡) 使用CDN
    1. 全局负载均衡(GSLB),
      1. DNS轮循 真实返回内容服务器的IP
      2. HTTP重定向 只适用于HTTP协议 返回GSLB的IP,先建立GSLP的连接,再302跳转于目标服务器
      3. 三角传输 适用于不能使用HTTP协议的场景 同2
    2. 服务器负载均衡(SLB)

CDN工作流程:

  1. 当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。
  2. CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户。
  3. 用户向CDN的全局负载均衡设备发起内容URL访问请求。
  4. CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。
  5. 区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址。
  6. 全局负载均衡设备把服务器的IP地址返回给用户。
  7. 用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。

建立TCP链接

通信双方建立连接之前,需要先进行三次握手

三次握手:

  1. 客户端会向服务端发送一个报文请求建立连接
    1. 数据包中,SYN标志位是1
    2. TCP规定了SYN等于1时,不能携带数据,但是需要消耗一个序号
    3. 初始序号seq=x ,表示传送数据时的第一个数据字节的序号是x(seq是一个随机值)
    4. 客户端进入SYN_SEND状态
  2. 服务器端在接收到这个报文之后,会进行同步确认。
    1. 服务端在接收到这个请求报文段后,如果同意则会发回一个ACK包(确认包)进行应答
    2. SYN、ACK = 1
    3. 服务端会选择一个ISN序列号(随机值,此处拟为y)放入seq,即seq=y
    4. 将确认序列号ack设为客户端发来的ISN+1,即 ack=x+1
      1. 即回复对方确认收到了序列号为x开始的包,且希望下次的数据从x+1的位置开始
    5. 服务器进入SYN_RCVD状态
  3. 客户端收到确认同步报文之后,会对服务服务器再进行一次确认,即再次发送一个报文
    1. 客户端再次发送给服务一个ACK包
    2. SYN=0,ACK=1
    3. 确认序号ack等于服务端发来的ISN序列号+1 即 ack=y+1
    4. ISN序列号 为 x+1 即 seq=x+1
    5. 客户端进入ESTAB_LISHED状态
  4. 至此,双方可以通信

image.png

HTTPS

部分摘自:juejin.cn/post/689562… 正常情况下,如果我们是使用的是http,在此时就会进入传输阶段了,如果使用了https,则还需要TLS握手的过程 TLS握手过程中,客户端和服务端会共同执行以下操作

  • 指定使用的TLS版本
  • 确定使用的加密套件
  • 通过数据签名验证服务器的身份
  • 握手成功后生成会话密钥

三种不同的握手方式:

  1. RSA握手

早期的TLS密钥交换法都是使用RSA算法,它的握手流程是这样子的

  1. 浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。
  2. 服务器接收到,立即返回 server_random,确认好双方都支持的加密套件 以及数字证书 (证书中附带公钥 Public key certificate)。
  3. 浏览器接收,先验证数字证书。若通过,接着使用加密套件的密钥协商算法 RSA 算法生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。
  4. 服务器用私钥解密这个被加密后的 pre_random,参考 “非对称加密”。

现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random。两者都用筛好的加密套件中的加密方法混合这三个随机数,生成最终的密钥。

  1. TLS1.2握手(主流)

主流的TLS1.2握手是DH握手

  1. 同RSA
  2. 服务器接收到,立即返回 server_random,确认好双方都支持的加密套件以及数字证书 (证书中附带公钥)。同时服务器利用私钥将 client_random,server_random,server_params 签名,生成服务器签名。然后将签名和 server_params 也发送给客户端。 这里的 server_params 为 DH 算法所需参数。
  3. 浏览器接收,先验证数字证书和 签名。若通过,将 client_params 传递给服务器。+这里的 client_params 为 DH 算法所需参数。
  4. 现在客户端和服务器都有 client_params、server_params 两个参数,因 ECDHE 计算基于 “椭圆曲线离散对数”,通过这两个 DH 参数就能计算出 pre_random。

现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random,后续步骤与 RSA 握手一致。

  1. TLS1.3(相对最安全)
  1. 浏览器向服务器发送 client_params,client_random,TLS 版本和供筛选的加密套件列表。
  2. 服务器返回:server_random、server_params、TLS 版本、确定的加密套件方法以及证书。+浏览器接收,先验证数字证书和签名。+现在双方都有 client_params、server_params,可以根据 ECDHE 计算出 pre_random 了。

最后,集齐三个参数,生成最终秘钥。

获取数据

至此,客户端与服务端已经成功建立了连接,接下来,浏览器会向服务器发送相应的请求以获取需要展示的页面数据。 服务器在收到客户端的这个请求之后,即是会返回相应的资源(nginx、apache、IIS 等 / cdn)

解析响应数据

客户端接收到浏览器返回的数据的时候,会根据返回的状态码做一个判断,如果返回的是301、302则是需要进行重定向,重定的地址一般都会放在请求头的Location中(一切会从头开始) 如果使用的是chrome,则还需要通过 SafeBrowsing 来查检我们需要访问的站点是否是恶意站点(我们通常看到的警告页面) 如果拿到的状态码是2xx,则代表响应成功(渲染进程进入提交等待阶段),接下来会判断返回的资源是否可以缓存,通常也会有相应的请求头来进行判断 在接收到资源后,还需要对资源进行解码,根据资源类型content-type决定该资源需要如何处理。

特殊情况:我们输入的url指向的其实一文件下载的资源,这个时候,下载文件的请求会被浏览器进程移交给浏览器的下载管理器,此时,我们的URL访问流程全部结束

之后网络线程会通过UI线程要开始工作了,UI线程会创建一个渲染进程来为页面的渲染作准备工作。

当用户输入完地址后我们整体划分,以下几个步骤: 2)由HTML解释器和JavaScript引擎解释构建文档对象模型。 3)由Css解释器构建渲染树(CSSOM 树) 4)CSSOM和DOM组合形成(RenderTree) 5)由webKit根据层次结构创建RenderLayer树 6)绘制

页面渲染

浏览器进程会通过IPC管理将数据传输给渲染进程,渲染进程在收到确认的消息后,就会和网络进程建立数据传输管理道,开始执行接下来的数据解析等工作。 浏览器渲染页面的过程主要是解析html文档组成标签节点树,解析css形成样式规则树,标签节点树和样式规则树共同组成渲染树,浏览器最终显示渲染树形成页面。

构建DOM树

  1. 浏览器会读取html的原始字节,由html解析器根据文件的指定编码将之转换成字符
  2. 根据w3c html5规范对解析出的字符进行分析,并解析成令牌(<div><html>等)以及其它尖括号内的字符串 每个令牌都具有特殊含义和一组规则
  3. 对这些令牌进行词法分析,转成DOM节点对象并定义属性和规则
  4. 解析器会维护一个解析栈,栈底为document,也就是DOM树的根节点
  5. 然后根据节点对象关系按顺序依次向解析栈添加,形成DOM树

解析过程中遇到没有async和defer的script标签引用入时,会暂停解析过程,同时通过网络线程加载文件,文件加载后切换至js引擎执行相应代码,代码执行完成后再切换回渲染引擎继续渲染流程 DOM阻塞问题:

  • CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。
  • JS 阻塞 DOM 解析,但浏览器会"偷看"DOM,预先下载相关资源。
  • 浏览器遇到 script标签 且没有 defer 或 async 属性的标签时,会触发页面渲染,如果前面 CSS 资源尚未加载完毕,浏览器就会等待它加载完毕再执行脚本。

构建CSSOM(CSS对象模型)

构建完DOM树之后,需要为DOM节点计算样式,即是开始构建CSSOM

mdn的解释:

CSS 对象模型和 DOM 是相似的。DOM 和 CSSOM 是两棵树. 它们是独立的数据结构。浏览器将 CSS 规则转换为可以理解和使用的样式映射。浏览器遍历 CSS 中的每个规则集,根据 CSS 选择器创建具有父、子和兄弟关系的节点树。 与 HTML 一样,浏览器需要将接收到的 CSS 规则转换为可以使用的内容。因此,它重复了 HTML 到对象的过程,但对于 CSS。 CSSOM 树包括来自用户代理样式表的样式。浏览器从适用于节点的最通用规则开始,并通过应用更具体的规则递归地优化计算的样式。换句话说,它级联属性值。 构建 CSSOM 非常非常快,并且在当前的开发工具中没有以独特的颜色显示。相反,开发人员工具中的“重新计算样式”显示解析 CSS、构建 CSSOM 树和递归计算计算样式所需的总时间。在 web 性能优化方面,它是可轻易实现的,因为创建 CSSOM 的总时间通常小于一次 DNS 查询所需的时间。

这个格式化好的结果,会被存储于document.styleSheets中 具体的转换过程:

  • 转换样式表中的属性值,使其标准化:比如,在编写代码时使用的是十六进制的颜色,需要转换成 rgb 的格式,李兵老师的课程有说把 em 单位转为 px 单位,bold 转为 700 ,我打开 Chrome 的开发者工具看并没有转,这里跟老师的描述有点出入。
  • 计算出 DOM 树中节点的样式:主要通过继承规则和层叠规则来进行计算。计算完成之后输出每个 DOM 节点的样式,并保存到 ComputedStyle 的结构内。
  • 继承规则:子节点如果没有设置 font-size、color、font-family 几个属性的样式,那么可以继承父节点的样式,如果父节点也没有设置其样式,那么默认使用 UserAgent 样式。注意:只有可继承属性才能继承。
  • 层叠规则:它是一个定义了如何合并来自多个源的属性值的算法。

Layout阶段

在webkit版本里,这个会生成一个RenderTree,树中所有布局节点继承自RenderObject

我们熟悉的行内流、块流、字体等都是在这一步处理完成的 DOM和CSSDOM都拿到之后,二者的结合会诞生LayoutObject Tree,树中所有的布局节点全部继承自LayoutObject类

这一阶段的主要功能其实就是 针对输入的DOM与CSSDOM,计算样式,输出LayoutObjct treePaintLayer tree(主要作用是处理层叠上下文关系),从而得到每个节点所对应的尺寸与位置。

绘制阶段