从输入URL到页面渲染出来的过程(详细)
一、 URL 解析
1. 输入内容合成地址
用户输入URL,浏览器会根据用户输入的信息判断是搜索还是网址。
如果是搜索内容,就将搜索内容(会对内容进行字符编码等操作)+ 默认搜索引擎合成新的URL;
如果用户输入的内容符合URL规则,浏览器就会根据URL协议,在这段内容上加上协议合成合法的
2. HSTS(HTTP Strict Transport Security)
因为 http 存在安全隐患(比如:明文传输,http 劫持),所以建议通过 HSTS 强制客户端使用 HTTPS。
关于HSTS和http劫持可以看下这两篇文章:
你所不知道的 HSTS
什么是HTTP劫持?
3. 其他操作
- 安全检查
比如访问某网站出现安全警告
- 访问限制
之前国产浏览器限制 996.icu - 黑客篡改网址
经过黑客封装的浏览器,可能会对链接做些修改,比如淘宝客的推广码换成自己的
4. 检查缓存
- 强制缓存
根据Cache-Control、Expires(优先使用Cache-Control)判断资源是否过期。
过期,则走协商缓存流程;
未过期,则从缓存中获取,不走网络请求,状态码200,Size显示disk cache或memory cache。 - 协商缓存
需要发起 http 请求,流程为:DNS查询 => TCP连接 => 处理请求 => 接受响应。
服务器根据 ETag/If-None-Match、Last-Modified 和 If-Modified-Since(优先使用 ETag) 判断。
过期:返回最新资源,状态码200
未过期:返回缓存中的资源,状态码304。
二、DNS(Domain Name System) 查询
1. 浏览器 DNS 缓存查询
浏览器会按照一定的频率缓存 DNS 记录,所以会先检查是否在缓存中,没有则调用系统库函数进行查询。
2. 操作系统(OS)级 DNS 缓存查询
会先检查自己本地的hosts文件是否有这个网址映射关系,
如果有,就先调用这个IP地址映射,完成域名解析。
如果没有,浏览器会发出一个 DNS请求到本地DNS服务器
3. 本地 DNS 服务器查询
本地 DNS 服务器(TCP/ip 参数中设置的首选DNS服务器)会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向根 DNS 服务器进行查询。
本地区域名服务器通常性能都会很好,它们一般都会缓存域名解析结果,当然缓存时间是受域名的失效时间控制的,一般缓存空间不是影响域名失效的主要因素。大约90%的域名解析都到这里就已经完成了,所以LDNS主要承担了域名的解析工作。
4. 根 DNS 服务器查询
根 DNS 服务器如果没有记录具体的域名和IP地址的对应关系,会通知本地 DNS 服务器到域服务器上去继续查询,并给出域服务器的地址。这种过程是迭代的过程。
5.顶级域名(TLD)服务器查询
本地DNS服务器继续向域服务器发出请求,域服务器收到请求之后,告诉本地 DNS 服务器域名解析服务器的地址。
在前面所有步骤没有缓存的情况下,下面这个图很好的诠释了整个流程:
关于 DNS 劫持
DNS劫持方法
- 本机DNS劫持
攻击者通过某些手段使用户的计算机感染上木马病毒,或者恶意软件之后,恶意修改本地DNS配置,比如修改本地hosts文件,缓存等 - 路由DNS劫持
很多用户默认路由器的默认密码,攻击者可以侵入到路由管理员账号中,修改路由器的默认配置 - 攻击DNS服务器
直接攻击DNS服务器,例如对DNS服务器进行DDOS攻击,可以是DNS服务器宕机,出现异常请求,还可以利用某些手段感染dns服务器的缓存,使给用户返回来的是恶意的ip地址
DNS的防范
- 加强本地计算机病毒检查,开启防火墙等,防止恶意软件,木马病毒感染计算机
- 改变路由器默认密码,防止攻击者修改路由器的DNS配置指向恶意的DNS服务器
- 企业的话可以准备两个以上的域名,一旦一个域名挂掉,还可以使用另一个
- 用HTTP DNS 代替 Local DNS
三、HTTP请求
1.建立TCP连接
- 客户端发送的TCP报文中标志位SYN置1,初始序号seq=x(随机选择)。Client进入SYN_SENT状态,等待Server确认。
- 服务器收到数据包后,根据标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个初始序号seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
- Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server。Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
2. 发起 HTTP 请求
建立起安全的加密信道后,浏览器开始发送 HTTP/HTTPS 请求,请求格式:
- 请求报头(Request Header):请求方法、请求头、目标地址、遵循的协议等等
- 请求主体(其他参数,Get 请求没有)
3. 返回 HTTP 响应
服务器检查客户端的报文头中是否有缓存信息If-None-Match、If-Modified-Since、ETag等,验证缓存是够有效,有效返回304和缓存中的资源,无效返回200和资源
4. 维持连接
完成一次 HTTP 请求后,服务器并不是马上断开与客户端的连接。
在 HTTP/1.1 中,Connection: keep-alive 是默认启用的,表示持久连接,以便处理不久后到来的新请求,无需重新建立连接而增加慢启动开销,提高网络的吞吐能力。
在反向代理软件 Nginx 中,持久连接超时时间默认值为 75 秒,如果 75 秒内没有新到达的请求,则断开与客户端的连接。
同时,浏览器每隔 45 秒会向服务器发送 TCP keep-alive 探测包,来判断 TCP 连接状况,如果没有收到 ACK 应答,则主动断开与服务器的连接。
注意,HTTP keep-alive 和 TCP keep-alive 虽然都是一种保活机制,但是它们完全不相同,一个作用于应用层,一个作用于传输层。
5. TCP 断开连接
- Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- Server收到FIN后,发送一个ACK给Client,确认序号为u + 1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- Client收到FIN后,Client进入TIME_WAIT状态(主动关闭方才会进入该状态),接着发送一个ACK给Server,确认序号为w + 1,Server进入CLOSED状态,完成四次挥手。
关于 TIME_WAIT 过渡到 CLOSED 状态说明: 从 TIME_WAIT 进入 CLOSED 需要经过 2MSL,其中 MSL 就叫做 最长报文段寿命(Maxinum Segment Lifetime),根据 RFC 793 建议该值这是为 2 分钟,也就是说需要经过 4 分钟,才进入 CLOSED 状态。
为什么要等待呢?
为了这种情况: B向A发送 FIN = 1 的释放连接请求,但这个报文丢失了, A没有接到不会发送确认信息, B 超时会重传,这时A在 WAIT_TIME 还能够接收到这个请求,这时再回复一个确认就行了。(A收到 FIN = 1 的请求后 WAIT_TIME会重新记时)
另外服务器B存在一个保活状态,即如果A突然故障死机了,那B那边的连接资源什么时候能释放呢? 就是保活时间到了后,B会发送探测信息, 以决定是否释放连接
为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
四、浏览器解析
Renderer 进程负责页面的解析工作,Renderer 进程通过在主线程中持有的 Blink 实例边接收边解析 HTML 内容,每次从网络缓冲区中读取 8KB 以内的数据,浏览器自上而下逐行解析 HTML 内容。
1. 解析 HTML 成 DOM 树
解码
传输回来的其实都是一些二进制字节数据,浏览器需要根据文件指定编码(例如UTF-8)转换成字符串,也就是HTML 代码。
预解析
预解析做的事情是提前加载资源,减少处理时间,它会识别一些会请求资源的属性,比如img标签的src属性,并将这个请求加到请求队列中。
符号化
符号化是词法分析的过程,将输入解析成符号,HTML 符号包括,开始标签、结束标签、属性名和属性值。
它通过一个状态机去识别符号的状态,比如遇到<,>状态都会产生变化。
构建树
注意:符号化和构建树是并行操作的,也就是说只要解析到一个开始标签,就会创建一个 DOM 节点。
在上一步符号化中,解析器获得这些标记,然后以合适的方法创建DOM对象并把这些符号插入到DOM对象中。
浏览器容错机制
你从来没有在浏览器看过类似"语法无效"的错误,这是因为浏览器去纠正错误的语法,然后继续工作。
事件
当整个解析的过程完成以后,浏览器会通过DOMContentLoaded事件来通知DOM解析完成。
2. 解析CSS 成 CSS 规则树
一旦浏览器下载了 CSS,CSS 解析器就会处理它遇到的任何 CSS,根据语法规范解析出所有的 CSS 并进行标记化,然后我们得到一个规则表。
CSS 匹配规则
在匹配一个节点对应的 CSS 规则时,是按照从右到左的顺序的,例如:div p { font-size :14px }
会先寻找所有的p
标签然后判断它的父元素是否为div
。
所以我们写 CSS 时,尽量用 id 和 class,千万不要过度层叠。
3. 合成渲染树(render tree)
其实这就是一个 DOM 树和 CSS 规则树合并的过程。
注意:渲染树会忽略那些不需要渲染的节点,比如设置了display:none的节点。
计算
通过计算让任何尺寸值都减少到三个可能之一:auto、百分比、px,比如把rem转化为px。
级联
浏览器需要一种方法来确定哪些样式才真正需要应用到对应元素,所以它使用一个叫做 specificity
的公式,这个公式会通过:
- 标签名、class、id
- 是否内联样式
- !important
然后得出一个权重值,取最高的那个。
渲染阻塞
当遇到一个script标签时,DOM 构建会被暂停,直至脚本完成执行,然后继续构建 DOM 树。
但如果 JS 依赖 CSS 样式,而它还没有被下载和构建时,浏览器就会延迟脚本执行,直至 CSS Rules 被构建。
所有我们知道:
- CSS 会阻塞 JS 执行
- JS 会阻塞后面的 DOM 解析
为了避免这种情况,应该以下原则:
- CSS 资源排在 JavaScript 资源前面
- JS 放在 HTML 最底部,也就是 前
另外,如果要改变阻塞模式,可以使用 defer 与 async,详见:为什么css要放头部,js放尾部以及async、defer该如何处理?
4. 布局与绘制(paint)
确定渲染树所有节点的几何属性,比如:位置、大小等等,最后输入一个盒子模型,它能精准地捕获到每个元素在屏幕内的准确位置与大小。
然后遍历渲染树,调用渲染器的 paint() 方法,绘制页面像素信息(本质上是一个像素填充的过程)。这个过程也出现于回流或一些不影响布局的 CSS 修改引起的屏幕局部重画,这时候它被称为重绘(Repaint)。实际上,绘制过程是在多个层上完成的,这些层我们称为渲染层(RenderLayer)。
5. 显示
1.浏览器会将各层的信息发送给GPU
2.GPU会将多个绘制后的渲染层按照恰当的重叠顺序进行合并,而后生成位图(composite),显示在屏幕上
参考:
https://www.jianshu.com/p/225cbd5ed927
https://zhuanlan.zhihu.com/p/133906695
https://zhuanlan.zhihu.com/p/80551769
https://segmentfault.com/a/1190000017184701
https://bubuzou.com/2020/12/16/browser-url/
http://www.dailichun.com/2018/03/12/whenyouenteraurl.html
https://www.jianshu.com/p/225cbd5ed927
https://www.cnblogs.com/ityouknow/p/6380603.html
https://juejin.cn/post/6844903863623876622
https://juejin.cn/post/6844903923694698504
https://juejin.cn/post/6844903966573068301
https://zhuanlan.zhihu.com/p/103162095