《游览器工作原理和实践》笔记(下)
整理自极客时间《游览器工作原理和实践》
游览器中的网络
HTTP/1
-
HTTP/0.9
-
最初的HTTP主要用于学术交流,需求很简单——用来在网络之间传递HTML超文本的内容,所以被称为超文本传输协议。
-
完整的请求流程

-
客户端先根据IP地址、端口和服务器建立TCP连接,这个过程就涉及三次握手。
-
建立连接后,会发送一个GET请求行信息,如GET/index.html用来获取index.html。
-
服务器接收请求信息后,读取对应HTML文件并将数据以ASCII字符流返回给客户端。
-
-
相关特点
-
只有请求行,没有请求头和请求体。
-
服务器也没有返回头信息。
-
返回的文件内容是以ASCII字符流来传输的。
-
-
-
HTTP/1.0
-
随着1994年底出现的拨号上网服务和网景推出的一款浏览器,万维网进入了高速的发展阶段,随之而来的是万维网联盟(W3C)和HTTP 工作组(HTTP-WG)的创建,它们致力于HTML的发展和HTTP的改进。
-
新发展面临着新需求,首先在浏览器中展示的不单是HTML文件了,还包括了JS、CSS、图片、音频和视频等不同类型的文件。因此支持多种类型的文件下载是HTTP/1.0的一个核心诉求,而且文件格式不仅仅局限于ASCII编码,还有很多其他类型编码的文件。
-
面临的具体问题
-
文件越来越大,应该减轻传输性能。
-
浏览器需要知道响应类型,然后才能针对性处理。
-
万维网是全球范围的,需要国际化的支持。
-
为了能够准确地读取文件,浏览器需要知道文件的编码类型。
-
请求有时候可能无法处理或者处理出错,需要引入状态码。
-
为了减轻服务器的压力,应该提供Cache机制,用来缓存已经下载过的数据。
-
服务器需要统计客户端的基础信息,应该加入了用户代理的字段。
-
-
问题的解决
-
HTTP/1.0引入了请求头和响应头,它们是以为Key-Value形式保存的,在HTTP发送请求时,会带上请求头信息,服务器返回数据时,会先返回响应头信息。
-
客户端和服务端根据请求头和响应头所携带的信息,进行对应需求的处理,满足了新的需求所面临的问题。
-
-
-
HTTP/1.1
-
改进持久连接
-
HTTP/1.0每进行一次HTTP通信,都需要经历建立TCP连接、传输HTTP数据和断开TCP连接三个阶段,如果传输文件多,数据量大,这无疑会增加大量无谓的开销。
-
HTTP/1.1中增加了持久连接的方法,它的特点是在一个TCP连接上可以连续传输多个HTTP请求,只要浏览器或者服务器没有明确断开连接,那么该TCP连接会一直保持。
-
持久连接在HTTP/1.1中是默认开启的,所以你不需要专门为了持久连接去HTTP请求头设置信息,如果你不想要采用持久连接,可以在HTTP请求头中加上Connection: close。
-
目前浏览器中对于同一个域名,默认允许同时建立6个TCP持久连接。
-
-
不成熟的HTTP管线化
-
持久连接虽然能减少TCP的建立和断开次数,但是它需要等待前面的请求返回之后,才能进行下一次请求。
-
如果TCP通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求,这就是著名的队头阻塞问题。
-
HTTP/1.1中试图通过管线化技术来解决队头阻塞的问题,也就是将多个HTTP请求整批提交给服务器的技术,不过服务器依然需要根据请求顺序来回复浏览器的请求。
-
FireFox、Chrome都做过管线化的试验,但是由于各种原因,它们最终都放弃了管线化技术。
-
-
提供虚拟主机的支持
-
在HTTP/1.0中,每个域名绑定了一个唯一的IP地址,因此一个服务器只能支持一个域名。
-
随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个IP地址。
-
因此,HTTP/1.1的请求头中增加了Host字段,用来表示当前的域名地址,这样服务器就可以根据不同的Host值做不同的处理。
-
-
对动态生成的内容提供了完美支持
-
在设计HTTP/1.0时,需要在响应头中设置完整的数据大小,如Content-Length: 901,这样浏览器就可以根据设置的数据大小来接收数据。
-
不过随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。
-
HTTP/1.1通过引入Chunk transfer机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。
-
-
客户端Cookie、安全机制
-
HTTP/2
-
HTTP/1.1的核心改进
-
增加了持久连接
-
浏览器为每个域名最多同时维护6个TCP持久连接
-
使用CDN的实现域名分片机制
-
-
HTTP/1.1的主要问题
-
对带宽的利用率却并不理想
-
问题的原因
-
TCP慢启动
-
一旦一个TCP连接建立之后,就进入了发送数据状态,刚开始TCP协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。
-
慢启动是TCP为了减少网络拥塞的一种策略,它会带来性能问题,是因为它延迟了页面关键资源的传输,推迟了页面的渲染。
-
-
竞争固定带宽
-
当通过域名分片后,TCP连接变得很多,在下载过程中,当发现带宽不足的时候,各个TCP连接就需要动态减慢接收数据的速度。
-
这些TCP连接的下载资源当中,有些是页面渲染的关键资源,有些是非关键资源,但是多条TCP连接之间又不能协商下载的优先级,这样就有可能影响那些关键资源的下载速度了。
-
-
队头阻塞
-
使用持久连接时,虽然能公用一个TCP管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。
-
在浏览器处理生成页面的过程中,是非常希望能提前接收到数据的,但队头阻塞使得这些数据不能并行请求,所以队头阻塞是很不利于浏览器优化的。
-
-
-
-
HTTP/2对HTTP/1.1问题的改进
-
TCP慢启动/竞争固定带宽
- 只使用一个TCP长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个TCP连接竞争带宽所带来的问题。
-
队头阻塞
- 多路复用
-
-
HTTP/2的多路复用
-
示意图

-
从图中可以看到,每个请求都有一个对应的ID,如stream1表示index.html的请求,stream2表示foo.css的请求。
-
这样在浏览器端,就可以随时将请求发送给服务器了,而服务器端接收到这些请求后,会根据自己的喜好来决定优先返回哪些内容。
-
服务器之所以可以随意发送,是因为每份数据都有对应的ID,浏览器接收到之后,会筛选出相同ID的内容,将其拼接为完整的HTTP响应数据。
-
多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,服务器可以暂停之前的请求来优先处理关键资源的请求。
-
-
实现原理

-
从图中可以看出,HTTP/2添加了一个二进制分帧层。
-
HTTP/2的请求和接收过程如下
-
首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是POST方法,那么还要有请求体。
-
这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求ID编号的帧,通过协议栈将这些帧发送给服务器。
-
服务器接收到所有帧之后,会将所有相同ID的帧合并为一条完整的请求信息。
-
然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
-
同样,二进制分帧层会将这些响应数据转换为一个个带有请求ID编号的帧,经过协议栈发送给浏览器。
-
浏览器接收到响应帧之后,会根据ID编号将帧的数据提交给对应的请求。
-
-
-
-
HTTP/2的其他特性
-
设置请求优先级
- HTTP/2提供了请求优先级,可以在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。
-
服务器推送
-
可以直接将数据提前推送到浏览器。
-
比如在接收到HTML请求后,附带将要使用的CSS文件和JS文件一并发送给浏览器,这样游览器解析完HTML后就可以直接使用了。
-
-
头部压缩
-
无论是HTTP/1.1还是HTTP/2,它们都有请求头和响应头,这是浏览器和服务器的通信语言。
-
当请求很多的时候,压缩请求头和响应头,那么传输效率就能得到大幅提升。
-
-
HTTP/3
-
HTTP/2的缺陷
-
TCP的队头阻塞
-
虽然HTTP/2解决了应用层面的队头阻塞问题,不过HTTP/2和HTTP/1.1一样,依然是基于TCP协议的,而TCP最初是为了单连接而设计的,在协议层面依然存在阻塞问题。
-
HTTP/1.1协议栈中TCP数据传输
-
正常情况下

-
丢包情况下

-
-
HTTP/2多路复用

-
通过该图,我们知道在HTTP/2中,多个请求是跑在一个TCP管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该TCP连接中的所有请求。
-
这不同于HTTP/1.1,使用HTTP/1.1时,浏览器为每个域名开启了6个TCP连接,如果其中的1个TCP连接发生了队头阻塞,那么其他的5个连接依然可以继续传输数据。
-
所以随着丢包率的增加,HTTP/2的传输效率也会越来越差。有测试数据表明,当系统达到了2%的丢包率时,HTTP/1.1的传输效率反而比HTTP/2表现得更好。
-
-
-
TCP建立连接的延时
-
网络延迟
- 简称RTT(Round Trip Time),我们把从浏览器发送一个数据包到服务器,再从服务器返回数据包到浏览器的整个往返时间称为RTT。
-
延迟的动作
-
TCP有三次握手动作,会产生握手延迟。
-
如果使用HTTPS的话,还需要使用TLS协议进行安全传输,而使用TLS也需要一个握手过程,这样就需要有两个握手延迟过程。
-
-
延迟的RTT消耗
-
在建立TCP连接的时候,需要和服务器进行三次握手来确认连接成功,也就是说需要在消耗完1.5个RTT之后才能进行数据传输。
-
进行TLS连接,TLS有两个版本——TLS1.2和TLS1.3,每个版本建立连接所花的时间不同,大致是需要1~2 个RTT。
-
如果浏览器和服务器的物理距离较近,那么1个RTT时间可能在10毫秒以内,也就是说总共要消耗掉30~40毫秒,但如果服务器相隔较远,1个RTT就可能需要100毫秒以上了,这种情况下整个握手过程需要 300~400毫秒,这时用户就能明显地感受到“慢”了。
-
-
-
TCP协议僵化
-
中间设备的僵化
-
我们知道互联网是由多个网络互联的网状结构,为了能够保障互联网的正常工作,我们需要在互联网的各处搭建各种设备,这些设备就被称为中间设备。
-
中间设备有很多种类型,并且每种设备都有自己的目的,它们包括了路由器、防火墙、NAT、交换机等。
-
这些中间设备通常依赖一些很少升级的软件,这些软件使用了大量的TCP特性,这些功能被设置之后就很少更新了。
-
所以,如果我们在客户端升级了TCP协议,但是当新协议的数据包经过这些中间设备时,它们可能不理解包的内容,于是这些数据就会被丢弃掉,这就是中间设备僵化,它是阻碍TCP更新的一大障碍。
-
除了中间设备僵化外,操作系统也是导致TCP协议僵化的另外一个原因,因为TCP协议都是通过操作系统内核来实现的,应用程序只能使用不能修改。
-
通常操作系统的更新都滞后于软件的更新,因此要想自由地更新内核中的TCP协议也是非常困难的。
-
-
-
-
QUIC协议
-
HTTP/2存在一些比较严重的与TCP协议相关的缺陷,但由于TCP协议僵化,我们几乎不可能通过修改TCP协议自身来解决这些问题,那么解决问题的思路是绕过TCP协议,发明一个TCP和UDP之外的新的传输协议。
-
但是这也面临着和修改TCP一样的挑战,因为中间设备的僵化,这些设备只认TCP和UDP,如果采用了新的协议,新协议在这些设备同样不被很好地支持,因此,HTTP/3选择了一个折衷的方法,基于UDP实现了类似于TCP的多路数据流、传输可靠性等功能,我们把这套功能称为QUIC协议。
-
关于HTTP/2和HTTP/3协议栈的比较

-
QUIC协议集合的功能
-
实现了类似TCP的流量控制、传输可靠性的功能。虽然UDP不提供可靠性的传输,但QUIC在UDP的基础之上增加了一层来保证数据可靠性传输,提供了数据包重传、拥塞控制以及其他一些TCP中存在的特性。
-
集成了TLS加密功能。目前QUIC使用的是TLS1.3,相较于早期版本TLS1.3有更多的优点,其中最重要的一点是减少了握手所花费的RTT个数。
-
实现了快速握手功能。由于QUIC是基于UDP的,所以QUIC可以实现使用0-RTT或者1-RTT来建立连接,这意味着QUIC可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
-
实现了HTTP/2中的多路复用功能。和TCP不同,QUIC实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。

- 实现了数据流的单独传输,也就解决了TCP中队头阻塞的问题。
-
-
-
HTTP/3的挑战
-
第一,从目前的情况来看,服务器和浏览器端都没有对HTTP/3提供比较完整的支持。
- Chrome虽然在数年前就开始支持Google版本的QUIC,但是这个版本的QUIC和官方的QUIC存在着非常大的差异。
-
第二,部署HTTP/3也存在着非常大的问题。
- 因为系统内核对UDP的优化,远远没有达到TCP的优化程度,这也是阻碍QUIC的一个重要原因。
-
第三,中间设备僵化的问题。
- 这些设备对UDP的优化程度远远低于TCP,据统计使用QUIC协议时,大约有3%~7%的丢包率。
-
游览器安全
同源策略
-
浏览器的安全划分
-
Web页面安全
-
浏览器系统安全
-
浏览器网络安全
-
-
web页面的安全策略:同源策略
-
相关概念
-
如果两个URL的协议、域名和端口都相同,我们就称这两个URL同源。
-
两个不同的源之间若想要相互访问资源或者操作DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。
-
-
限制表现
-
DOM层面。限制了来自不同源的JS脚本对当前DOM对象读和写的操作。
-
数据层面。限制了不同源的站点读取当前站点的Cookie、IndexDB和LocalStorage等数据。
-
网络层面。限制了通过XMLHttpRequest等方式将站点的数据发送给不同源的站点。
-
-
-
安全和便利性的权衡
-
为了web世界的开发和便利,浏览器出让了同源策略下的一些安全性。
-
具体表现
-
页面中可以嵌入第三方资源
-
它带来了一些安全性问题,比如典型的XSS攻击。
-
为了解决XSS攻击,浏览器中引入了内容安全策略,简称CSP。
-
CSP的核心思想是,让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联JS代码,这些手段可以大大减少XSS攻击。
-
-
跨域资源共享和跨文档消息机制
-
为了解决网络和请求的同源限制,引入了跨域资源共享(CORS),使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。
-
为了解决两个不同源的DOM之间进行通信,引入了跨文档消息机制,可以通过window.postMessage的JS接口来和不同源的DOM进行通信。
-
-
-
XSS攻击
-
什么是XSS攻击
-
XSS全称是Cross Site Scripting,为了与CSS区分开来,故简称XSS,翻译过来就是跨站脚本。
-
XSS攻击是指黑客往HTML文件中或者DOM中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。
-
-
XSS攻击的影响
-
窃取Cookie信息。恶意JS可以通过document.cookie获取Cookie信息,然后通过Ajax加CORS将数据发送给恶意服务器,恶意服务器拿到用户的Cookie信息之后,就可以在其他电脑上模拟用户的登录,然后进行转账等操作。
-
监听用户行为。恶意JS可以使用addEventListener接口来监听键盘事件,比如可以获取用户输入的信用卡等信息,将其发送到恶意服务器,黑客掌握了这些信息之后,又可以做很多违法的事情。
-
修改DOM。伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息。
-
生成页面浮窗广告。严重地影响用户体验。
-
-
XSS攻击的类型
-
存储型XSS攻击
-
参考图

-
攻击方式
-
首先,黑客利用站点漏洞将一段恶意JS代码提交到网站的数据库中。
-
然后,用户向网站请求包含了恶意JS脚本的页面。
-
当用户浏览该页面的时候,恶意脚本就会将用户的Cookie信息等数据上传到服务器。
-
-
案例分析
-
2015年喜马拉雅就被曝出了存储型XSS漏洞,起因是在用户设置专辑名称时,服务器对关键字过滤不严格,比如可以将专辑名称设置为一段JS代码。

-
当这段JS代码被提交时,喜马拉雅的服务器就会将它保存到数据库中,然后当用户打开黑客设置的专辑时,这段代码就会在用户的页面里执行。

-
当用户打开黑客设置的专辑页面时,服务器也会将这段恶意JS代码返回给用户,因此这段恶意脚本就在用户的页面中执行了。
-
恶意脚本读取到Cookie,然后通过Ajax将用户的Cookie数据上传到黑客的服务器,如下图所示:

-
黑客拿到了用户Cookie信息之后,就可以利用Cookie信息在其他机器上登录该用户的账号(如下图),并利用用户账号进行一些恶意操作。

-
-
-
反射型XSS攻击
-
攻击方式
-
在一个反射型XSS攻击过程中,恶意JS脚本属于用户发送给网站请求中的一部分,随后网站又把恶意JS脚本返回给用户。
-
当恶意JS脚本在用户页面中被执行时,黑客就可以利用该脚本做一些恶意操作。
-
-
案例分析
-
相关代码
/* 后台逻辑代码 */ var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { res.render('index', { title: 'Express',xss:req.query.xss }); }); module.exports = router; /* 后台页面模板 */ <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> <div> <%- xss %> </div> </body> </html> -
上面这两段代码,第一段是路由,第二段是视图,作用是将URL中xss参数的内容显示在页面。
-
在本地演示下,打开
http://localhost:3000/?xss=123,页面中展示123(如下图),是正常的,没有问题。
-
但当打开
http://localhost:3000/?xss=<script>alert('你被xss攻击了')</script>这段URL时,其结果如下图所示:
-
在现实生活中,黑客经常会通过QQ群或者邮件等渠道诱导用户去点击这些恶意链接,所以对于一些链接我们一定要慎之又慎。
-
Web服务器不会存储反射型XSS攻击的恶意脚本,这是和存储型XSS攻击不同的地方。
-
-
-
基于DOM的XSS攻击
-
攻击方式
- 黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改HTML页面的内容。
-
手段类型
-
通过WiFi路由器劫持。
-
通过本地恶意软件来劫持。
-
-
特点
-
不牵涉到页面Web服务器。
-
在Web资源传输过程,或者在用户使用页面的过程中,修改Web页面的数据。
-
-
-
-
如何阻止XSS攻击
-
攻击类型分析
-
存储型XSS攻击和反射型XSS攻击都需要经过Web服务器处理,因此可以认为它们的漏洞是服务端的安全漏洞。
-
基于DOM的XSS攻击全部都是在浏览器端完成的,因此基于DOM的XSS攻击是属于前端的安全漏洞。
-
-
阻止攻击的方式
-
服务器对输入脚本进行过滤或转码
-
过滤
code:<script>alert('你被xss攻击了')</script>- 这段代码过滤后,只留下了:
code:- 这样,当用户再次请求该页面时,由于
<script>标签的内容都被过滤了,所以这段脚本在客户端是不可能被执行的。
-
转码
code:<script>alert('你被xss攻击了')</script>- 还是上面那段代码,经过转码之后,
<script>标签被转换为<script>,因此即使这段脚本返回给页面,页面也不会执行这段脚本。
- 还是上面那段代码,经过转码之后,
-
-
充分利用CSP策略
-
实施严格的CSP可以有效地防范XSS攻击
-
CSP的几个功能
-
限制加载其他域下的资源文件,这样即使黑客插入了一个JS文件,这个JS文件也是无法被加载的。
-
禁止向第三方域提交数据,这样用户数据也不会外泄。
-
禁止执行内联脚本和未授权的脚本。
-
上报机制,这样可以帮助我们尽快发现有哪些XSS攻击,以便尽快修复问题。
-
-
-
使用HttpOnly属性
-
由于很多XSS攻击都是来盗用Cookie的,因此还可以通过使用HttpOnly属性来保护我们Cookie的安全。
-
通常服务器可以将某些Cookie设置为HttpOnly标志,HttpOnly是服务器通过HTTP响应头来设置的。
set-cookie: NID=xxxxxxxxx; expires=Sat, 18-Apr-2020 06:52:22 GMT; path=/; domain=.google.com; HttpOnly -
使用HttpOnly标记的Cookie 只能使用在HTTP请求过程中,所以无法通过JS来读取这段Cookie,也就无法获取到用户的关键数据。
-
-
-
CSRF攻击
-
什么是CSRF攻击
-
CSRF英文全称是Cross-site request forgery,所以又称跨站请求伪造,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。
-
CSRF攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。
-
和XSS不同的是,CSRF攻击不需要将恶意代码注入用户的页面,仅仅是利用服务器的漏洞和用户的登录状态来实施攻击。
-
-
CSRF攻击的类型
-
以转账接口为例
#同时支持POST和Get #接口 https://time.geekbang.org/sendcoin #参数 ##目标用户 user ##目标金额 number -
自动发起Get请求
<!DOCTYPE html> <html> <body> <h1>黑客的站点:CSRF攻击演示</h1> <img src="https://time.geekbang.org/sendcoin?user=hacker&number=100"> </body> </html>-
这是黑客页面的HTML代码,在这段代码中,黑客将转账的请求接口隐藏在img标签内,欺骗浏览器这是一张图片资源。
-
当该页面被加载时,浏览器会自动发起img的资源请求,如果服务器没有对该请求做判断的话,那么服务器就会认为该请求是一个转账请求,于是用户账户上的100就被转移到黑客的账户上去了。
-
-
自动发起POST请求
<!DOCTYPE html> <html> <body> <h1>黑客的站点:CSRF攻击演示</h1> <form id='hacker-form' action="https://time.geekbang.org/sendcoin" method=POST> <input type="hidden" name="user" value="hacker" /> <input type="hidden" name="number" value="100" /> </form> <script> document.getElementById('hacker-form').submit(); </script> </body> </html>-
黑客在他的页面中构建了一个隐藏的表单,该表单的内容就是极客时间的转账接口.
-
当用户打开该站点之后,这个表单会被自动执行提交;当表单被提交之后,服务器就会执行转账操作。
-
因此使用构建自动提交表单这种方式,就可以自动实现跨站点POST数据提交。
-
-
引诱用户点击链接
-
还有一种方式是诱惑用户点击黑客站点上的链接,这种方式通常出现在论坛或者恶意邮件上,黑客会采用很多方式去诱惑用户点击链接。
<div> <img width=150 src=http://images.xuejuzi.cn/1612/1_161230185104_1.jpg> </img> </div> <div> <a href="https://time.geekbang.org/sendcoin?user=hacker&number=100" taget="_blank"> 点击下载美女照片 </a> </div>- 页面上放了一张美女图片,下面放了图片下载地址,而这个下载地址实际上是黑客用来转账的接口,一旦用户点击了这个链接,那么他的极客币就被转到黑客账户上了。
-
-
-
CSRF攻击的条件
-
CSRF漏洞——目标站点一定要有CSRF漏洞。
-
用户登录状态——用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态。
-
打开第三方站点——需要用户打开一个第三方站点,可以是黑客的站点,也可以是一些论坛。
-
-
如何防止CSRF攻击
-
CSRF攻击的关键一点是要能找到服务器的漏洞,所以对于CSRF攻击,我们主要的防护手段是提升服务器的安全性。
-
防御方法
-
利用Cookie的SameSite属性
-
黑客需要利用用户的登录状态来发起CSRF攻击,而Cookie正是浏览器和服务器之间维护登录状态的一个关键数据,因此要阻止CSRF攻击,我们就要考虑在Cookie防御。
-
通常CSRF攻击都是从第三方站点发起的,要防止CSRF攻击,我们最好能实现从第三方站点发送请求时禁止Cookie的发送。
-
Cookie中的SameSite属性正是为了解决这个问题的,通过使用SameSite属性可以有效地降低CSRF攻击的风险。
-
-
验证请求的来源站点
-
由于CSRF攻击大多来自于第三方站点,因此可以在服务器端验证请求来源的站点,禁止来自第三方站点的请求。
-
Referer是HTTP请求头中的一个字段,记录了该HTTP请求的来源地址,但其中包含了详细路径信息,在一些场景下是不适合将来源URL暴露给服务器的。
-
Origin属性代表源,只包含了域名信息,并没有包含具体的URL路径,服务器应该采取的策略是,优先判断Origin,如果请求头中没有包含Origin属性,再根据实际情况判断是否使用Referer值。
-
-
CSRF Token
-
第一步,在浏览器向服务器发起请求时,服务器生成一个CSRF Token,它其实是服务器生成的字符串,然后将该字符串植入到返回的页面中。
-
第二步,在浏览器端如果要发起转账的请求,那么需要带上页面中的CSRF Token,然后服务器会验证该Token是否合法。
-
如果是从第三方站点发出的请求,那么将无法获取到CSRF Token的值,所以即使发出了请求,服务器也会因为CSRF Token不正确而拒绝请求。
-
-
-
安全沙箱
-
浏览器架构是如何影响到操作系统安全的
-
在页面安全和操作系统安全之间,浏览器本身的漏洞是单进程浏览器的一个主要问题,如果浏览器被曝出存在漏洞,那么在这些漏洞没有被及时修复的情况下,黑客就有可能通过恶意的页面向浏览器中注入恶意程序。
-
最常见的攻击方式是利用缓冲区溢出,通过浏览器漏洞进行的攻击是可以入侵到浏览器进程内部的,可以读取和修改浏览器进程内部的任意内容,还可以穿透浏览器,在用户的操作系统上悄悄地安装恶意软件、监听用户键盘输入信息以及读取用户硬盘上的文件内容。
-
-
安全视角下的多进程架构
-
架构示意图

-
从上图可知,浏览器被划分为浏览器内核和渲染内核两个核心模块,其中浏览器内核是由网络进程、浏览器主进程和GPU进程组成的,渲染内核就是渲染进程。
-
所有的网络资源都是通过浏览器内核来下载的,下载后的资源会通过IPC将其提交给渲染进程(浏览器内核和渲染进程之间都是通过IPC来通信的)。
-
然后渲染进程会对这些资源进行解析、绘制等操作,最终生成一幅图片。
-
但是渲染进程并不负责将图片显示到界面上,而是将最终生成的图片提交给浏览器内核模块,由浏览器内核模块负责显示这张图片。
-
-
安全沙箱
-
由于渲染进程需要执行DOM解析、CSS解析、网络图片解码等操作,如果渲染进程中存在系统级别的漏洞,那么以上操作就有可能让恶意的站点获取到渲染进程的控制权限,进而又获取操作系统的控制权限,这对于用户来说是非常危险的。
-
因为网络资源的内容存在着各种可能性,所以浏览器会默认所有的网络资源都是不可信的,都是不安全的,因为谁也不能保证浏览器不存在漏洞,只要出现漏洞,黑客就可以通过网络内容对用户发起攻击。
-
基于以上原因,我们需要在渲染进程和操作系统之间建一道墙,即便渲染进程由于存在漏洞被黑客攻击,但由于这道墙,黑客就获取不到渲染进程之外的任何操作权限。
-
将渲染进程和操作系统隔离的这道墙就是我们要聊的安全沙箱,安全沙箱最小的保护单位是进程。
-
浏览器中的安全沙箱是利用操作系统提供的安全技术,让渲染进程在执行过程中无法访问或者修改操作系统中的数据,在渲染进程需要访问系统资源的时候,需要通过浏览器内核来实现,然后将访问的结果通过IPC转发给渲染进程。
-
因为单进程浏览器需要频繁访问或者修改操作系统的数据,所以单进程浏览器是无法被安全沙箱保护的,而现代浏览器采用的多进程架构使得安全沙箱可以发挥作用。
-
-
安全沙箱如何影响各个模块功能
-
由于安全沙箱限制了渲染进程对操作系统资源的访问和修改,因此渲染进程没有读写操作系统的功能。
-
渲染进程和浏览器内核各自的职责

- 从图中可以看出,由于渲染进程受到安全沙箱的保护,因此需要把在渲染进程内部涉及到和系统交互的功能都转移到浏览器内核中去实现。
-
具体影响的体现
-
持久存储
-
渲染进程内部有访问Cookie和有上传文件的需求,为了解决这些文件访问需求,现代浏览器将读写文件的操作全部放在了浏览器内核中实现,然后通过IPC将操作结果转发给渲染进程。
-
存储Cookie数据的读写。
- 通常浏览器内核会维护一个存放所有Cookie的Cookie数据库,然后当渲染进程通过JS来读取Cookie时,渲染进程会通过IPC将读取Cookie的信息发送给浏览器内核,浏览器内核读取Cookie之后再将内容返回给渲染进程。
-
一些缓存文件的读写也是由浏览器内核实现的,比如网络文件缓存的读取。
-
-
网络访问
-
同样有了安全沙箱的保护,在渲染进程内部也是不能直接访问网络的,如果要访问网络,则需要通过浏览器内核。
-
不过浏览器内核在处理URL请求之前,会检查渲染进程是否有权限请求该URL,比如检查XMLHttpRequest或者Fetch是否是跨站点请求。
-
或者检测HTTPS的站点中是否包含了HTTP的请求。
-
-
用户交互
-
通常情况下,如果你要实现一个UI程序,操作系统会提供一个界面给你,该界面允许应用程序与用户交互,允许应用程序在该界面上进行绘制。
-
Windows提供的是HWND,Linux提供的X Window,我们就把HWND和X Window统称为窗口句柄,应用程序可以在窗口句柄上进行绘制和接收键盘鼠标消息。
-
在现代浏览器中,由于每个渲染进程都有安全沙箱的保护,所以在渲染进程内部是无法直接操作窗口句柄的,这也是为了限制渲染进程监控到用户的输入事件。
-
渲染进程不能直接访问窗口句柄的影响
-
第一点,渲染进程需要渲染出位图。为了向用户显示渲染进程渲染出来的位图,渲染进程需要将生成好的位图发送到浏览器内核,然后浏览器内核将位图复制到屏幕上。
-
第二点,操作系统没有将用户输入事件直接传递给渲染进程,而是将这些事件传递给浏览器内核。然后浏览器内核再根据当前浏览器界面的状态来判断如何调度这些事件,如果当前焦点位于浏览器地址栏中,则输入事件会在浏览器内核内部处理;如果当前焦点在页面的区域内,则浏览器内核会将输入事件转发给渲染进程。
-
-
之所以这样设计,就是为了限制渲染进程有监控到用户输入事件的能力,所以所有的键盘鼠标事件都是由浏览器内核来接收的,然后浏览器内核再通过IPC将这些事件发送给渲染进程。
-
-
-
-
站点隔离(Site Isolation)
-
所谓站点隔离,是指Chrome将同一站点(包含了相同根域名和相同协议的地址)中相互关联的页面放到同一个渲染进程中执行。
-
目前所有操作系统都面临着两个A级漏洞——幽灵(Spectre)和熔毁(Meltdown),这两个漏洞是由处理器架构导致的,很难修补,黑客通过这两个漏洞可以直接入侵到进程的内部,如果入侵的进程没有安全沙箱的保护,那么黑客还可以发起对操作系统的攻击。
-
为了保护A级漏洞入侵渲染进程,Chrome将标签级的渲染进程重构为iframe级的渲染进程,然后严格按照同一站点的策略来分配渲染进程,这就是Chrome中的站点隔离。
-
通过站点隔离技术,就可以将恶意的iframe隔离在恶意进程内部,使得它无法继续访问其他iframe进程的内容,因此也就无法攻击其他站点了。
-
-
总结
-
通过安全沙箱,将操作系统和渲染进程隔离,使得即便渲染进程由于漏洞被攻击,也不会影响到操作系统。
-
通过站点隔离,解除了最初iframe渲染进程一体造成的安全缺陷,实现了渲染进程间的危险隔离。
-
HTTPS
-
中间人攻击
-
起初设计HTTP协议的目的很单纯,就是为了传输超文本文件,那时候也没有太强的加密传输的数据需求,所以HTTP一直保持着明文传输数据的特征。
-
在网络数据传输过程中,将HTTP数据提交给TCP层之后,数据会经过用户电脑、WiFi路由器、运营商和目标服务器,在这中间的每个环节中,数据都有可能被窃取或篡改。

-
从上图可以看出,我们使用HTTP传输的内容很容易被中间人窃取、伪造和篡改,通常我们把这种攻击方式称为中间人攻击。
-
-
引入安全层
-
鉴于HTTP明文传输过程缺乏安全性,因此需要引入加密方案,我们可以在TCP和HTTP之间插入一个安全层,所有经过安全层的数据都会被加密或者解密。

-
从图中可以看出,HTTPS并非是一个新的协议,它只是在HTTP协议栈中加入了一个安全层,通常HTTP直接和TCP通信,HTTPS则先和安全层通信,然后安全层再和TCP层通信。
-
HTTPS所有的安全核心都在安全层,它不会影响到上面的HTTP协议,也不会影响到下面的TCP/IP,因此要搞清楚HTTPS是如何工作的,就要弄清楚安全层是怎么工作的。
-
-
安全层设计
-
第一版:使用对称加密
-
所谓对称加密,是指加密和解密都使用的是相同的密钥。

-
问题
-
传输client/service-random的过程是明文的,黑客也可以拿到协商的加密套件和双方随机数。
-
由于利用随机数合成密钥的算法是公开的,所以黑客拿到随机数之后,也可以合成密钥,这样数据依然可以被破解。
-
-
-
第二版:使用非对称加密
-
示意图

-
服务器会将其中的一个密钥通过明文的形式发送给浏览器,我们把这个密钥称为公钥,服务器自己留下的那个密钥称为私钥。
-
公钥是每个人都能获取到的,而私钥只有服务器才能知道,不对任何人公开,服务端用私钥加密的内容,可以通过它的公钥进行解密。
-
-
问题
-
非对称加密的效率太低
-
无法保证服务器发送给浏览器的数据安全
-
-
-
第三版:对称加密和非对称加密搭配使用
-
在传输数据阶段依然使用对称加密,但是对称加密的密钥我们采用非对称加密来传输。

- 其中的pre-master是经过公钥加密之后传输的,所以黑客无法获取到pre-master,这样黑客就无法生成密钥,也就保证了黑客无法破解传输过程中的数据了。
-
-
第四版:添加数字证书
-
通过对称和非对称混合方式,我们完美地实现了数据的加密传输,但是黑客通过DNS劫持可以劫持我们访问的服务器,所以还需要引入CA权威机构保证服务器可信性。
-
负责颁发证书的机构称为CA(Certificate Authority),颁发的证书就称为数字证书(Digital)。

- 通过引入数字证书,我们就实现了服务器的身份认证功能,这样即便黑客伪造了服务器,但是由于证书是没有办法伪造的,所以依然无法欺骗用户。
-
-
-
总结
-
HTTPS使用了对称和非对称的混合加密方式,解决了数据传输安全的问题。
-
HTTPS通过中间机构CA给服务器颁发的数字证书,解决了浏览器对服务器的信任问题。
-
拓展工具篇
HTTPS验证证书
-
数字证书申请流程

-
填写表单信息(公钥、站点资料、公司资料等),提交给CA机构
-
CA机构审核,通过对表单的操作(Hash计算,私钥加密等),输出数字签名
-
CA机构将数字签名返写到表单,返还给申请对象
-
-
浏览器验证证书的流程
-
验证证书的有效期
-
验证数字证书是否被吊销了
-
验证极客时间的数字证书是否是CA机构颁发的

-
-
浏览器是怎么获取到CA公钥的
-
当部署HTTP服务器时,除了部署当前域名的数字证书外,还需要部署CA机构的数字证书,它包括了CA公钥,以及CA机构的一些基础信息。
-
在建立HTTPS链接时,服务器会将这两个证书一同发送给浏览器,于是浏览器就可以获取到CA的公钥了。
-
如果有些服务器没有部署CA数字证书,那么浏览器还可以通过网络去下载CA证书,不过这种方式多了一次证书下载操作,会拖慢首次打开页面的请求速度,一般不推荐使用。
-
当拿到这两个证书后,就可以来验证对应域名的数字证书可靠性了。
-
-
如何证明CA机构的合法性
-
这里并没有一个非常好的方法来证明CA的合法性,妥协的方案是,直接在操作系统中内置这些CA机构的数字证书

-
我们将所有CA机构的数字证书都内置在操作系统中,这样当需要使用某CA机构的公钥时,我们只需要依据CA机构名称,就能查询到对应的数字证书了,然后再从数字证书中取出公钥。
-
可以看到,这里有一个假设条件,浏览器默认信任操作系统内置的证书为合法证书,虽然这种方式不完美,但是却是最实用的一个。
-
不过这种方式依然存在问题,因为在实际情况下,CA机构众多,因此操作系统不可能将每家CA的数字证书都内置进操作系统。
-
-
数字证书链
-
于是人们又想出来一个折中的方案,将颁发证书的机构划分为两种类型,根CA(Root CAs)和中间CA(Intermediates CAs)。
-
通常申请者都是向中间CA去申请证书的,而根CA作用就是给中间CA做认证。一个根CA会认证很多中间的CA,而这些中间CA又可以去认证其他的中间CA。
-
因此,每个根CA机构都维护了一个树状结构,一个根CA下面包含多个中间CA,而中间CA又可以包含多个中间CA。
-
-
-
如何验证根证书的合法性
-
浏览器的判断策略很简单,它只是简单地判断这个根证书在不在操作系统里面,如果在,那么浏览器就认为这个根证书是合法的,如果不在,那么就是非法的。
-
如果某个机构想要成为根CA,并让它的根证书内置到操作系统中,那么这个机构首先要通过WebTrust国际安全审计认证。
-
目前通过WebTrust认证的根CA有Comodo、geotrust、rapidssl、symantec、thawte、digicert等。
-
这些根CA机构的根证书都内置在各大操作系统中,只要能从数字证书链往上追溯到这几个根证书,浏览器就会认为使用者的证书是合法的。
-
要想在操作系统内部内置根证书却并不容易,这需要通过WebTrust认证,这个认证审核非常严格。
-
-
HTTPS的安全性
-
浏览器默认信任操作系统内置的根证书,这也会带来一个问题,如果黑客入侵了你的电脑,那么黑客就有可能往你系统中添加恶意根数字证书,那么当你访问黑客站点的时候,浏览器甚至有可能会提示该站点是安全的。
-
因此,HTTPS并非是绝对安全的,采用HTTPS只是加固了城墙的厚度,但是城墙依然有可能被突破。
-
浏览上下文组
-
标签页之间的连接
-
通过a标签来和新标签页建立连接
// A页面 <a href="https://www.b.org/" target="_black">进入B页面</a>- 通过A页面点击到B页面,新的B标签页的window.opener值就是A页面的window,可以通其来操作A页面,这样我们可以说,这两个标签页是有连接的。
-
通过js中的window.open方法来和新标签页建立连接
// A页面 new_window = window.open("http://www.b.org")- 通过js方式,可以在当前标签页中通过new_window来控制新标签页,还可以在新标签页中通过window.opener来控制当前标签页,这两个标签页之间也是有连接的。
-
-
浏览上下文组
-
有连接的两个标签页,不论它们是否属于同一站点,他们之间都能通过opener来连接,这一类具有相互连接关系的标签页被称为浏览上下文组。
-
通常情况下,我们把一个标签页所包含的内容,诸如window对象,历史记录,滚动条位置等信息称为浏览上下文,这些相互连接起来的浏览上下文一起构成了浏览上下文组。
-
也就是说,如果在A标签页中,通过链接打开了多个新标签页,不管这几个新标签页是否是同一站点,他们都和A标签页构成了浏览上下文组,因为这些标签页中的opener都指向了A标签页。
-
-
游览器分配渲染进程的策略
-
Chrome浏览器会将浏览上下文组中属于同一站点的标签页分配到同一个渲染进程中。
-
计算标签页使用的渲染进程数目

-
分配的例外
-
当a标签中的rel属性值使用了noopener和noreferrer时,跳转的标签页与来源页切断了连接。
-
noopener是告诉浏览器,将通过这个a链接打开的标签页中的opener值设置为null。
-
noreferrer是告诉浏览器,新打开的标签页不要有引用关系。
-
因此,带ref="noopener noreferrer"属性值的a标签页,跳转过去的标签页会被独立分配渲染进程。
-
-
站点隔离
-
Chrome浏览器已经默认实现了站点隔离的功能,这意味着标签页中的iframe也会遵守同一站点的分配原则。
-
如果iframe页和标签页是同一站点并且有连接关系,那么iframe页会和当前标签页运行在同一个渲染进程中。
-
如果iframe页和标签页不属于同一站点,那么iframe会运行在单独的渲染进程中。

-
-
强调
-
同一站点,只代表了根域名相同,但是域名却不一定相同,比如:根域名geekbang.org和time.geekbang.org的网站域名就是不同的,它们不属于同源的。
-
有连接而且属于同一站点的两个标签页虽然会共享渲染进程,但是如果两个标签页不属于同源站点,还是会受到同源策略限制,无法通过opener来操作父标签页。
-
-
任务调度
-
单消息队列的队头阻塞问题
-
渲染主线程按照先进先出的顺序执行消息队列中的任务,图示图如下

-
基于这种单消息队列的架构,一旦不重要的任务在前头堆积太多,就会影响高优先级任务的执行,如果这个任务是交互任务的话,那势必就影响了体验。

-
-
Chromium对单队列头阻塞问题的解决
-
第一次迭代:引入一个高优先级队列(解决优先级)

-
通过分出优先级队列,然后在渲染进程中引入一个任务调度器,就可以灵活的按队列优先级来执行任务了。
-
不过大多数任务需要保持其相对执行顺序,如果将用户输入的消息或者合成消息添加进多个不同优先级的队列中,那么这种任务的相对执行顺序就会被打乱。
-
比如有可能出现还未处理用户的输入事件,就合成了该事件要显示的图片,因此我们需要让一些相同类型的任务保持其相对执行顺序。
-
-
第二次迭代:根据消息类型来实现消息队列(解决相对执行顺序)
-
为不同类型的任务创建不同优先级的消息队列

-
这种策略已经相当实用了,但是依然存在着问题,那就是这几种消息队列的优先级都是固定的,任务调度器会按照这种固定好的静态优先级来分别调度任务。
-
页面生存周期大致分为两个阶段,加载阶段和交互阶段。虽然在交互阶段,采用上述静态优先级策略没有太大问题,但是在加载阶段,如果依然要优先执行用户输入事件和合成事件,那么页面的解析速度将会被拖慢。
-
-
-
第三次迭代:动态调度策略(适应页面阶段)
-
示意图

-
动态调度的实施
-
加载阶段
-
这个阶段的诉求是尽管看到页面,至于交互和合成并不是这个阶段的核心诉求。
-
因此在加载阶段将页面解析,js执行等任务调整为优先级最高的队列,降低交互合成这些队列的优先级。
-
-
交互阶段
-
游览器图像显示
-
在显卡中有一块叫着前缓冲区的地方,这里存放着显示器要显示的图像,显示器会按照一定的频率来读取这块前缓冲区,并将前缓冲区中的图像显示在显示器上。
-
不同的显示器读取的频率是不同的,通常情况下是60HZ,也就是说显示器会每间隔1/60秒就读取一次前缓冲区。
-
如果浏览器要更新显示的图片,就会将新生成的图片提交到显卡的后缓冲区中,提交完成之后,GPU会将后缓冲区和前缓冲区互换位置,也就是前缓冲区变成了后缓冲区,后缓冲区变成了前缓冲区,这就保证了显示器下次能读取到GPU中最新的图片。
-
-
图像更新的问题
-
我们会发现,显示器从前缓冲区读取图片和浏览器生成新的图像到后缓冲区的过程是不同步的。

-
如果渲染进程生成的帧速比屏幕的刷新率慢,那么屏幕会在两帧中显示同一个画面,当这种断断续续的情况持续发生时,用户将会很明显地察觉到动画卡住了。
-
如果渲染进程生成的帧速率实际上比屏幕刷新率快,那么也会出现一些视觉上的问题,比如当帧速率在100fps而刷新率只有60Hz的时候,GPU所渲染的图像并非全都被显示出来,这就会造成丢帧现象。
-
就算屏幕的刷新频率和GPU更新图片的频率一样,由于它们是两个不同的系统,所以屏幕生成帧的周期和VSync的周期也是很难同步起来的。
-
-
游览器的解决
-
当显示器将一帧画面绘制完成后,并在准备读取下一帧之前,显示器会发出一个垂直同步信号(vertical synchronization)给GPU,简称VSync。
-
浏览器就会在接受到VSync信号后,将其同步给浏览器进程,浏览器进程再将其同步到对应的渲染进程,渲染进程接收到VSync信号之后,就可以准备绘制新的一帧了。

-
从上图可以看出,当渲染进程接收到用户交互的任务后,接下来大概率是要进行绘制合成操作。
-
因此我们可以设置,当在执行用户交互的任务时,将合成任务的优先级调整到最高。
-
接下来,处理完成DOM,计算好布局和绘制,就需要将信息提交给合成线程来合成最终图片了,然后合成线程进入工作状态。
-
进入合成阶段后,我们就可以把下个合成任务的优先级调整为最低,并将页面解析、定时器等任务优先级提升。
-
在合成完成之后,会提交合成消息给渲染主线程,如果合成操作非常快,比如从用户发出消息到完成合成操作只花了8毫秒,因为VSync同步周期是16.66(1/60)毫秒,那么这个VSync时钟周期内就不需要再次生成新的页面了。
-
-
-
空闲阶段
-
从合成结束到下个VSync周期内,就进入了一个空闲时间阶段,可以在这段空闲时间内执行一些不那么紧急的任务。
-
比如V8的垃圾回收,或者通过window.requestIdleCallback()设置的回调任务等,都会在这段空闲时间内执行。
-
-
-
-
第四次迭代:任务饿死
-
以上方案看上去似乎完美了,不过依然存在一个问题,那就是在某个状态下,如果一直有新的高优先级的任务加入到队列中,这样就会导致其他低优先级的任务得不到执行,这称为任务饿死。
-
Chromium为了解决任务饿死的问题,给每个队列设置了执行权重,也就是如果连续执行了一定个数的高优先级的任务,那么中间会执行一次低优先级的任务,这样就缓解了任务饿死的情况。
-
-
-
Chromium任务调度的启示
-
CSS动画是由渲染进程自动处理的,所以渲染进程会让CSS渲染每帧动画的过程与VSync的时钟保持一致, 这样就保证了CSS动画的高效率执行。
-
为什么有了setTimeOut还要使用requestAnimationFrame?
- 因为js是由用户控制的,如果采用setTimeout来触发动画每帧的绘制,那么其绘制时机是很难和VSync时钟保持一致的,所以js中又引入了requestAnimationFrame,用来和VSync的时钟周期同步。
-
Chrome开发者工具
-
Chrome开发者工具

-
网络面板

-
网络面板中的详细列表
-
列表的属性

-
详细信息

-
单个资源的时间线
-
HTTP请求流程

-
HTTP请求在单个资源中的表示

-
Queuing(排队)
- 有很多原因导致请求排队:资源的优先级,TCP连接的最大数和网络进程在为数据分配磁盘空间
-
Stalled(停滞)
- 等待排队完成之后,在发起连接之前,还有一些原因可能导致连接过程被推迟
-
Proxy Negotiation(代理协商)
- 表示代理服务器连接协商所用的时间,使用到代理服务器时会有显示
-
Initial connection/SSL阶段(建立连接)
- 包括TCP连接时间,如果是HTTPS,则还包括SSL握手时间
-
Request sent(请求发送)
- 通常这个阶段非常快,因为只需要把浏览器缓冲区的数据发送出去就结束了,并不需要判断服务器是否接收到了
-
Waiting (TTFB)(等待响应)
-
被称为“第一字节时间”,TTFB是反映服务端响应速度的重要指标
-
对服务器来说,TTFB时间越短,就说明服务器响应越快
-
-
Content Download(内容下载)
- 从第一字节时间到接收到全部响应数据所用的时间
-
-
-
-
优化时间线上耗时项
-
排队(Queuing)时间过久
-
域名分片——将一个网站的资源放在多个域名下面,拓展TCP连接数
-
升级到HTTP2——HTTP2已经没有每个域名最多维护6个TCP连接的限制了
-
-
第一字节时间(TTFB)时间过久
-
服务器生成页面数据的时间过久——提高服务器的处理速度
-
网络的原因——利用CDN,提高服务器水平
-
发送请求头时带上了多余的用户信息——减少一些不必要的Cookie信息携带
-
-
Content Download时间过久
- 减少文件大小——压缩、去掉源码中不必要的注释等
-
如何使用Audits
-
使用前准备工作
-
目前多数需要FQ使用,否则生成报告弹窗会一直卡在那。
-
需要开启游览器隐身模式,这样能去除游览器扩展、浏览器缓存和Cookie的影响。
-
推荐使用Chrome Canary版本的游览器,采用最新技术构建的,它的开发者工具和浏览器特性都是最新的。
-
-
工作面板介绍

-
检测类型(Categories)
-
Performance,监测并分析Web性能
-
Progressive Web App,监测并分析PWA程序的性能
-
Best practices,监测并分析Web应用是否采用了最佳实践策略
-
Accessibility,监测并分析是否实施了无障碍功能
-
SEO,监测并分析Web应用是否采实施了搜素引擎优化
-
-
设备类型(Device)
-
Moblie,移动设备
-
Desktop,桌面环境
-
-
-
性能报告介绍
-
性能指标 (Metrics)

-
共有6项内容,它们分别对应了Web应用从加载到页面展示完成的这段时间中,各个阶段所消耗的时长。
-
在中间还有一个View Trace按钮,点击该按钮可以跳转到Performance标签,并且查看这些阶段在Performance中所对应的位置。
-
-
可优化项 (Opportunities)

- 页面中一些可以直接优化的部分,Audits给出这些提示来帮助你优化你的Web应用
-
手动诊断 (Diagnostics)

-
采集了一些可能存在性能问题的指标,它们可能会影响到页面的加载性能
-
Audits把这些详情列出来,帮助你依据实际情况,来手动排查每一项
-
-
运行时设置 (Runtime Settings)

- 展示运行时的一些基本数据
-
-
根据性能报告进行优化
-
页面加载过程

-
渲染进程在确认渲染请求后,会创建一个空白页面,这个创建的时间点称为First Paint,简称FP。
-
当页面中绘制了第一个像素时,我们把这个时间点称为First Content Paint,简称FCP。
-
当首屏内容完全绘制完成时,我们把这个时间点称为Largest Content Paint,简称LCP。
-
在FCP和LCP中间,还有一个First Meaningfull Paint,叫FMP,这个是首次有效绘制,由于计算复杂,而且容易出错,现在不推荐使用该指标。
-
-
优化性能指标 (Metrics)
-
首次绘制 (First Paint,FP)
-
表示渲染前,显示默认空白页时的时间
-
会由于网络原因,导致HTML资源文件加载过久
-
可针对网络和传输进行优化
-
-
首次有效绘制 (First Meaningfull Paint,FMP)
-
表示能有效绘制页面时的时间
-
基于关键资源的加载时间或js脚本的执行时间
-
可针对页面渲染进行优化
-
-
首屏时间 (Speed Index,也就是LCP时间)
-
表示完成首屏页面绘制所需要的时长
-
优化方向同上
-
-
首次CPU空闲时间 (First CPU Idle)
-
表示页面达到最小化可交互所需要的时长
-
优化方向同上
-
-
完全可交互时间 (Time to Interactive)
-
表示页面达到完全可交互的时长
-
可推迟执行一些和生成页面无关的js工作
-
-
最大估计输入延时 (Max Potential First Input Delay)
-
表示页面在加载最繁忙的阶段,窗口中响应用户输入所需的时间
-
可使用WebWorker来执行一些计算,从而释放主线程
-
还可以重构CSS选择器,以确保它们执行较少的计算
-
-
-
如何使用Performance
-
工作面板

-
区域1
-
设置Network来限制网速
-
设置CPU来限制CPU运算速度
-
-
区域2/3
-
黑色按钮是用来记录交互阶段性能数据的,需要手动停止录制过程。
-
箭头圈按钮用来记录加载阶段的性能数据,会自动停止录制过程。
-
-
-
报告面板
-
整体面板图

-
概览面板用于定位问题的时间节点,性能面板用于分析该时间节点内的性能数据。
-
比如概览面板中的FPS图表中出现了红色块,那么我们点击该红色块,性能面板就定位到该红色块的时间节点内。
-
如果需要想要查看事件范围更广的性能指标,只需要将鼠标放到时间线上,滚动鼠标滚轮就可以就行缩放了。
-
如果放大之后,要查看的内容如果超出了屏幕,那么你可以点击鼠标左键来拖动时间线,直到找到需要查看的内容。
-
-
概览面板
-
Performance将几个关键指标,诸如页面帧速(FPS)、CPU资源消耗、网络请求流量、V8内存使用量(堆内存)等,按照时间顺序做成图表的形式展现出来,这就是概览面板。
-
如果FPS图表上出现了红色块,那么就表示红色块附近渲染出一帧所需时间过久,帧的渲染时间过久,就有可能导致页面卡顿。
-
如果CPU图形占用面积太大,表示CPU使用率就越高,那么就有可能是因为某个js占用太多的主线程时间,从而影响其他任务的执行。
-
如果V8的内存使用量一直在增加,就有可能是某种原因导致了内存泄漏。
-
概览面板还展示加载过程中的几个关键时间节点,如FP、LCP、DOMContentLoaded、Onload等事件产生的时间点,它们体现在了几条不同颜色的竖线上。
-
-
性能面板
-
Main指标,记录渲染主线程的任务执行过程。
-
Compositor指标,记录了合成线程的任务执行过程。
-
GPU指标,记录了GPU进程主线程的任务执行过程。
-
Compositor指标,记录了合成线程的任务执行记录。
-
Raster指标,光栅化线程池,记录了GPU执行的多个光栅化任务。
-
GPU指标,记录了GPU进程中的任务执行。
-
Chrome_ChildIOThread指标,对应的是IO线程的上的任务记录。
-
Network指标,展示了页面中的每个网络请求所消耗的时长,并以瀑布流的形式展现。
-
Timings指标,记录了一些关键时间节点在何时产生的数据信息(诸如 FP、FCP、LCP)。
-
Frames指标,用来记录生成每帧的记录,包括了渲染出每帧时长、图层构造等信息。
-
Interactions指标,用来记录用户交互操作,比如点击鼠标、输入文字等交互信息。

-
展开main指标

-
一段段横条代表执行一个个任务,长度越长,花费的时间越多。
-
竖向代表该任务的执行记录。
-
-
-
详情面板
-
用于展示时间线上各个任务的详细信息。
-
可以在性能面板中选中性能指标中的任何历史数据,然后选中记录的细节信息就会展现在详情面板。

-
-
Performance中的Main指标
-
任务vs过程
-
Main指标记录的是渲染主线上所执行的全部任务,以及每个任务的详细执行过程。

-
每个灰色横条就对应了一个任务,灰色长条的长度对应了任务的执行时长。
-
灰线下面的横条就是一个个过程,同样这些横条的长度就代表这些过程执行的时长。
-
-
可以把任务看成是一个Task函数,在执行Task函数的过程中,它会调用一系列的子函数,这些子函数就是我们所提到的过程。

function A(){ A1() A2() } function Task(){ A() B() } Task()
-
-
分析页面加载过程
-
以一段代码为例
<html> <head> <title>Main</title> <style> area { border: 2px ridge; } box { background-color: rgba(106, 24, 238, 0.26); height: 5em; margin: 1em; width: 5em; } </style> </head> <body> <div class="area"> <div class="box rAF"></div> </div> <br> <script> function setNewArea() { let el = document.createElement('div') el.setAttribute('class', 'area') el.innerHTML = '<div class="box rAF"></div>' document.body.append(el) } setNewArea() </script> </body> </html> -
生成报告页后

-
导航阶段,该阶段主要从网络进程接收HTML响应头和HTML响应体。
-
解析HTML数据阶段,该阶段主要将接收到的HTML数据转换为DOM和CSSOM。
-
生成可显示的位图阶段,该阶段主要是利用DOM和CSSOM,经过计算布局、生成层树、生成绘制列表、完成合成等操作,生成最终的图片。
-
-
导航阶段

-
该任务的第一个子过程就是Send request,表示网络请求已被发送,然后该任务进入了等待状态。
-
接着由网络进程负责下载资源,当接收到响应头的时候,该任务便执行Receive Respone 过程,表示接收到HTTP的响应头了。
-
接着执行DOM事件:pagehide、visibilitychange和unload等事件,如果你注册了这些事件的回调函数,那么这些回调函数会依次在该任务中被调用。
-
这些事件被处理完成之后,接下来就接收HTML数据,这体现在了Recive Data过程,这个过程表示请求的数据已被接收,如果HTML数据过多,会存在多个Receive Data过程。
-
等到所有的数据都接收完成之后,渲染进程会触发另外一个任务,该任务主要执行Finish load过程,该过程表示网络请求已经完成。
-
-
解析HTML数据阶段

-
在ParserHTML的过程中,如果解析到了script标签,就进入了脚本执行过程,也就是图中的Evalute Script。
-
在Evalute Script过程中,先是脚本编译,也就是图中的Complie Script,编译好之后,就进入程序执行过程。
-
执行全局代码时,V8会先构造一个anonymous过程,在执行anonymous过程中,会调用 setNewArea,setNewArea中又调用了createElement,由于之后调用了document.append,该方法会触发DOM内容修改,所以又强制执行了ParserHTML过程生成的新的DOM。
-
DOM生成完成之后,会触发相关的DOM事件,比如典型的DOMContentLoaded,还有readyStateChanged。
-
之后,ParserHTML过程继续计算样式表,也就是Reculate Style,这就是生成CSSOM的过程,到此一个完整的ParserHTML任务就执行结束了。
-
-
生成可显示位图阶段

-
该过程中,渲染主线程首先执行了一些DOM事件,诸如readyStateChange、load、pageshow。
-
然后执行布局,这个过程对应图中的Layout。
-
接着更新层树 (LayerTree),这个过程对应图中的Update LayerTree。
-
有了层树之后,就需要为层树中的每一层准备绘制列表了,这个过程就称为Paint。
-
准备好每层绘制列表之后,就需要利用绘制列表来生成相应图层的位图了,这个过程对应图中的Composite Layers。
-
走到Composite Layers这步,主线程任务就完成了,接下来主线程会将合成任务完全教给合成线程来执行。

-
主线程执行到Composite Layers过程之后,便会将绘制列表等信息提交给合成线程,合成线程的执行记录可以通过Compositor指标来查看。
-
合成线程维护了一个Raster线程池,线程池中的每个线程称为Rasterize,用来执行光栅化操作,对应的任务就是Rasterize Paint。
-
当然光栅化操作并不是在Rasterize线程中直接执行的,而是在GPU进程中执行的,因此 Rasterize线程需要和GPU线程保持通信。
-
然后GPU生成图像,最终这些图层会被提交给浏览器进程,浏览器进程将其合成并最终显示在页面上。
-
-
-

浙公网安备 33010602011771号