浏览器渲染页面流程
- 在地址栏输入 url 进行访问
- 浏览器查找缓存(浏览器缓存-系统缓存-路由器缓存),若有缓存则直接使用
- 浏览器缓存会记录 DNS 一段时间
- 若浏览器缓存失效,则会查询操作系统记录,会保存最近的 DNS 访问记录
- 搜索路由器缓存
- 查找 ISP
- DNS 解析,得到 IP 地址
- 浏览器向服务器发起 TCP 连接,需要三次握手
- 连接成功,浏览器向服务器发起 http 请求获取资源
- 服务器收到请求,向浏览器发送响应
- 浏览器接收到 http 响应
- 浏览器读取内容,开始渲染,解析 html
- 生成 DOM 树,解析 css,生成 render 树,解析 js
- 交互
- 关闭 TCP 连接
浏览器缓存机制(HTTP缓存机制)
浏览器缓存存放在 4 个位置,依次获取:
-
service worker 浏览器背后的独立现成,因为涉及到请求拦截,所以需要 https 来保障安全
-
memery cache 内存中的缓存,主要是当前页面上已经抓取下来的资源,比如图片,样式,脚本等;会随着进程的释放而释放。
-
disk cache 存在硬盘中的缓存,根据 http 请求的 header 判断哪些资源需要缓存
-
push cache 推送缓存(http2.0),只在会话中存在,严格遵循 header 中的要求进行缓存
强缓存
向浏览器缓存查找请求结果,并根据该结果的缓存规则来确定是否使用。不会向浏览器发起请求,状态码 200。
强缓存通过:Expires 和 Cache-Control 来设置,Cache-Control 优先级更高。
Expires:HTTP/1.0。响应头字段。指定资源到期的时间,是服务器设置的一个时间点。
* 缺点:需要服务端时间和客户端时间对比,所以可能会出现时间不一致导致缓存失效
Cache-Control:HTTP/1.1。有以下取值:
* max-age: 缓存在多少秒之后失效
* public: 所有内容都可以被缓存(客户端和代理服务器都可缓存)
* private(默认值): 只能被客户端缓存
* no-cache: 客户端缓存内容,但是否使用缓存需要协商缓存来验证决定
(使用缓存的时候需要跟服务器校验一下内容是否一致)
* no-store: 不使用强制缓存,也不使用协商缓存
协商缓存
强缓存失效后,浏览器携带缓存标志,有服务器根据缓存标志决定是否使用缓存。状态码 304
* Last-Modified:HTTP/1.1。响应头字段。记录资源在服务器上最后修改的时间。浏览器接收后会缓存资源和时间
* If-Modified-Since:请求头字段。值是 Last-Modified。
* 服务端收到之后,会将它与资源最近一次更新时间做比较
* 若资源没有变化,返回 304 和空的响应体
* 若资源有变化,返回 200 和新的资源
缺点:时间是秒为单位,若更新的时间粒度小于秒,则不能返回更新后的资源;若只是打开了文件并没有做修改,但是最近更新时间也会改变,会被误认为做了修改。
* Etag:响应头字段。由服务器生成的当前资源的唯一标志,只要资源变化,标志就重新生成
* If-None-Match: 请求头字段。上一次的 Etag 值。
* 若与 Etag 匹配,则返回 304 和空的响应体
* 若不匹配,则返回 200 和新的资源
比较:
- 精度上,Etag 优于 Last-Modified
- 性能上,Last-Modified 优于 Etag。因为 Etag 需要算法生成一个 hash 值
- Etag 优先级更高
应用场景
- 频繁变动的资源
- Cache-Control: no-cache。不要使用强制缓存
- 配合 Etag 和 Last-Modified。 由服务端来验证资源是否有效
- 不常变动的资源
- Cache-Control: max-age=31536000
DNS 解析
1. 检查浏览器缓存
2. 检查操作系统缓存
3. 向本地域名服务器查询
* 一般是运营商(联通、电信)提供,或者校园网(在学校的机房)
4. 根域名解析
* 根域名服务器会返回「通用定级域名服务器」的地址,.com、.cn、.org、.edu等
* 通用定级域名服务器会找到 Name Server 服务器,注册域名的服务商的服务器会提供域名解析
5. 返回 ip 地址
三次握手
1. 客户端向服务端发送 SYN 报文(SYN=1,seq=x),进入 SYN_SENT 状态
2. 服务端收到之后,发送 SYN 报文(SYN=1, ACK=1, seq=y, ack=x+1), 进入 SYN_RCVD 状态
3. 客户端向服务端发送确认报文(ACK=1, seq=x+1, ack=y+1),双方进入 established 状态。
* 如果第三次握手的报文丢失,那么服务器会重传连接请求,超过重传次数,则断开半连接状态
名词解释
- SYN:请求连接/接收报文段
- ACK:确认报文段
- seq:发送的第一个字节序列号
- ack:期望下一次接收的第一个字节序列号 第三次握手是可以携带数据的。SYN=1 的步骤不能携带数据。
为什么是三次握手,不是两次: 第三次握手是为了防止之前传送超时的报文,传送到服务端之后引起误会。如果有一次客户端发给服务端的消息延迟了,客户端已经重新发了第二次消息,那第一次消息就是作废的消息了,如果只有两次握手,当超时的那次已请求发到服务端时,服务端会认为是客户端的连接请求,那么会给客户端回一次消息,如果只有两次握手,那握手就成功了,连接就建立了。服务端就会一直等着客户端那边的消息,浪费了资源。
四次挥手
1. 客户端发送 FIN 报文(FIN=1,seq=u),停止发送数据,主动关闭连接,进入 FIN_WAIT1 状态
2. 服务端收到之后发送 ACK 报文(ACK=1,seq=v, ack=u+1),服务端进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT2 状态
* 此时TCP处于半关闭状态,客户端到服务端的连接状态释放
* 服务端没有马上关闭连接是因为可能还有数据没传完,所以需要第三步
3. 服务端发送 FIN 报文(FIN=1,ACK=1, seq=w, ack=u+1),服务端进入 LAST_ACK 状态
4. 客户端发送 ACK 报文(ACK=1,seq=u+1,ack=w+1),客户端处于 TIME_WAIT 状态
* 等待时间为 2MSL(一个报文来回的时间),是为了确认服务端收到关闭连接的报文,
如果服务端没收到,那么会重传(第三步),然后确认收到客户端的 ACK 报文之后再关闭连接,
双方进入 CLOSE 状态
名词解释:
- FIN:连接中止
- ACK:确认报文段
- seq:发送的第一个字节序列号
- ack:期望下一次接受的第一个字节序列号
浏览器渲染机制
1. 将 HTML 解析成 DOM 树
* 深度遍历,解析完当前节点及其所有子节点之后,再解析兄弟节点
2. 将 css 解析成 cssom 树
3. 将 js 中通过 dom api 干预布局的逻辑,解析应用到布局中
4. DOM 树建立后,根据 css 构建绘图模型,形成 render 树
* 一些被 display: none 的节点不会出现在 render tree 中
5. 遍历 render tree,绘制页面
- 为了用户体验,渲染引擎会尽早的呈现页面,不会等到 html 解析完了再去构建布局,而是解析一部分内容就展示一部分内容,同时可能还在下载其他内容。
- 浏览器获取到 html 之后,会自上而下的加载并解析。
- 加载:获取其他资源
- 如果遇到 css 或者其他外部图片,则发起别的请求,是异步的,不会影响 html 的加载
- 如果遇到 js 文件,则渲染进程会被挂起,等 js 加载完成后再渲染(因为 js 里有可能会改变 dom)
- 回流:当 render tree 中任何元素的几何尺寸(宽高、字体)发生变化,则会触发回流,重新计算所有节点在页面中的位置
- 重绘:当render tree 中任何元素的样式属性(颜色)发生改变,就会重新绘制
- 渲染优化
- DOM 层次尽量简单
- 脚本放在 body 结束前
- 减少 dom 操作,避免触发回流
- 涉及多域名,可以开启域名预解析
<link rel="dns-prefetch" href="xxx.com">- http 页面下的所有 a 标签的链接都自动开启域名预解析
- https 的页面需要在 meta 里手动设置
<meta http-equiv="x-dns-prefetch-control" content="on">
HTTP 协议 / HTTPS 协议
- HTTP 协议:基于 TCP 实现的应用层协议
- HTTPS 协议:在 HTTP 上建立 TLS/SSL 加密层,并对传输数据进行加密
请求头
General:
Request Url:请求的资源
Request Method:请求方法
Status Code:状态码
Request header:
1. Accept:客户端能够接受的媒体类型(html/text,application/json)
2. Accept-Encoding:客户端的编码方式(gzip,deflate)
3. Accept-Language:客户端的语言()
4. Referer:告诉服务器请求是从哪个页面过来的
5. User-Agent:客户端的相关信息(型号,版本号)
6. Cookie
7. 缓存:Cache-Control、If-Modified-Since、If-None-Match等
8. Origin(CORS): 本地请求来自哪个域
9. Connection:管理持久连接 (keep-alive)
10. Date:创建HTTP报文的日期和时间
Response header:
1. Content-Type:资源的内体类型,同 Accept
2. Allow:服务端支持的请求方法
3. Content-Encoding: 服务端返回资源的编码方式,
4. 缓存:Cache-Control、Last-Modified、Etag
5. Access-Control-Allow-Origin(CORS): 指定能跨域的来源,* 表示所有来源都支持
6. Access-Control-Allow-Credentials(CORS): 是否允许跨域发送 cookie
7. Connection:管理持久连接 (keep-alive)
8. Date:创建HTTP报文的日期和时间
HTTPS 握手
第一次通信:目的是得到服务端的数字签名证书
1. 客户端发起 Client Hello(TLS 版本、加密算法、随机数(client Random))
2. 服务端发送 Server hello(server hello 的信息有的服务端会合并发送,有的会单条发送)
* 首先会确认是否支持客户端的加密算法,支持的话则会在 server hello 中返回确认的 TLS 版本号
* 证书(包含公钥)
* 随机数(server random)
第二次通信:目的是生成后续安全通信的「对称秘钥」
1. 客户端检查证书是否合法,域名与服务端是否匹配
2. 客户端生成一个新的随机数(pre-master),使用证书中的公钥加密
至此,客户端和服务端都同时拥有三个随机数(client random、server random、pre-master),可以生成「对称秘钥」,用于后续通信的加密解密
3. 客户端再给服务端发送 Change Cipher Spec,告诉服务端开始使用加密方式通信
4. 客户端将之前的信息做个摘要,用秘钥加密之后发送给服务端。验证加密算法的可行性以及校验之前的内容是否被篡改
5. 服务端回应 Change Cipher Spec 验证没问题,则可以继续通信
https 使用非对称加密对「对称秘钥」进行加密传输,建立通道之后的通信使用「对称加密」,保证通信的解密效率。
对称加密和非对称加密
对称加密:加密和解密使用相同的秘钥
- 无法验证发送者和接受者身份,存在身份冒用的危险
非对称加密:加密和解密使用不同的秘钥:公钥和私钥
- 公钥对外开放,私钥不开放
- 使用对方的公钥加密之后的内容,因为私钥只有对方持有,所以别人无法解密,保证了安全
- 数字签名:因为私钥只有一方持有,因此使用持有的秘钥加密后,只能用秘钥对应的公钥解密,所以可以确认对方的身份无误。
状态码
1xx:信息提示。表示临时的相应
100:继续
101:切换协议
2xx:success 成功状态码
200:OK,表示请求已被服务端正确处理
204:No Content,服务端已成功处理,但是无资源返回
206:Patial Content,服务端成功处理了部分 GET 请求
3xx:Redirection 重定向
301:Moved Permanently,永久重定向,资源已经被分配了新的 url
302:Found,临时重定向,资源临时分配了新的 url,后面还可能修改
303:See Other,临时重定向,应使用 GET 方法获取资源
304:Not Modified,表示资源未修改
307:Temporary Redirect,临时重定向,与 303 含义一致,但是会遵循标准,不会从 POST 变成 GET
4xx:客户端错误
400:Bad Request,请求报文中存在语法错误
401:Unauthorized,未经许可,请求要求身份验证
403:Forbidden,禁止访问,服务器拒绝请求
405:Method Not Allowed,请求方法不合法
5xx:服务端错误
500:Internet Server Error,服务器内部发生错误
502:Bad Gateway,服务器作为网关,接受不到上游响应
504:网关超时,服务器作为网关,没有及时从上游服务器收到响应
HTTP/1.0 和 HTTP/1.1
1. 长链接。1.1 支持一个 TCP 连接传送多个 HTTP 请求和详情,减少握手和挥手带来的延迟时间,
默认开启 connect:keep-alive;
2. 节约带宽。有的时候只需要对象的一部分,但是 1.0 会将整个对象传递过来。
1.1 支持断点续传,支持只传 header,如果服务器认为客户端有权限请求服务器,则返回100,
客户接收到100才开始把请求body发送到服务器;
如果返回401,客户端就可以不用发送请求body了节约了带宽
3. HOST 头部处理。在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,
因此,请求消息中的URL并没有传递主机名(hostname),HTTP1.0没有host域。
随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。
HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误(400 Bad Request)
4. 缓存处理。在HTTP1.0中主要使用header里的Cache-Control,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略
HTTP/1.1 和 HTTP/2.0
1. 多路复用。同一个 tcp 连接并发多个请求,每个 request 有对应的额 id,并发处理后的结果通过 id 对应
2. 头部数据压缩。通过 encoder 压缩头部,减少大小,双方持有一份header的索引表,避免重复header 的传输
3. 二进制格式。不同于 1.x 的文本格式,2.0 使用功能二进制格式传输,更健壮
4. 服务端推送。允许服务器主动推动内容给客户端
GET & POST
1. get 请求是幂等的;post 请求是不幂等的,有副作用
2. get 请求的数据可以做缓存
3. get 请求可以作为书签保存
4. get 产生一个数据包;post 有两个数据包,先发送 header,收到 100 continue 之后再发送 body
5. get 的参数是用户可见的;post 的参数是用户不可见的,所以不要在 get 请求里传敏感数据
四层协议
TCP 和 UDP
1. TCP 有连接;UDP 无连接
2. TCP 保证可靠的交付,无差错、不丢失、按序到达;UDP 尽最大努力交付,时效性高,适合实时通讯等场景
3. TCP 支持一对一,UDP 支持多对多
跨域
同源策略
协议、域名、端口任一个不同,就不同源
跨域的方式
JSONP
原理:利用页面上script 标签加载资源不受同源策略影响
- 将回调函数作为参数添加在 url 上,传给服务端
- 服务端收到参数后,执行函数并将返回的数据作为参数
- 客户端收到结果后,因为是脚本,所以会直接执行从而达到跨域的目的
- 兼容性好,但是只能支持 GET 请求
CORS CORS 详解
跨域资源共享
* Method:GET、HEAD、POST
* Header:Accept、Accept-Language、Accept-Content、Content-Type、Last-Event-ID
- 简单请求:满足上面两个条件就是简单请求
- 在请求头里携带 origin 字段
- 非简单请求:指对服务端有特殊要求的请求。不满足以上任何一条,就是非简单请求
- options 预检请求。先询问服务器,当前域名是否在许可名单内,以及可以使用的头部字段,等待服务端确认
- 若服务端否定了预检请求,则会返回一个响应,但是没有任何 CORS 相关的头部,这时浏览器就会认定跨域失败,从而报错
CORS 的相应头部
Access-Control-Allow-Origin: 服务器允许跨域的域名,设置为 * 表示不限制
Access-Control-Allow-Method: 服务器所支持的请求方法
Access-Control-Allow-Headers: 服务器所支持的头信息
Access-Control-Allow-Credentials: 是否允许传递 cookie
PostMessage
通过事件监听实现数据通信
- 父页面传递数据到子页面
父页面
iframe.contentWindow.postMessage(JSON.stringify(data), target)
子页面
window.addEventListener("message", (e) => {console.log(e.data)})
- 子页面传递数据到父页面
子页面
window.parent.postMessage(JSON.stringify(data), target);
iframe + window.name
window.name 在不同页面加载之后依然存在,所以可以利用这个特性传递数据,可以支持 2mb 的数据
iframe + document.domain
- 通过将父子页面的document.domain均设置为它们的父域来实现iframe内部元素的获取
- 缺点:这种方法只适用于不同子域的框架间的交互
cookie、sessionStorage、localStorage
生命周期
cookie: 可以设置失效时间,没有设置则关闭浏览器后失效
sessionStorage:关闭当前浏览器 tab 页签失效
localStorage:若不手动清理,则永久存在
数据容量
cookie:4KB
sessionStorage:5MB
localStorage:5MB
cookie 在每次 http 请求中都会携带;sessionStorage 和 localStorage 只存在客户端;
cookie
- 浏览器获取 cookie:
document.cookie - 浏览器设置 cookie:
document.cookie="a=1;b=2" - cookie 的属性
- name
- value
- Expires:设置 cookie 有效期,是一个时间点
- Max-age:设置 cookie 有效期,以秒为单位的时间段
- httpOnly:为true,则不能使用 js 获取,能防止 xss 攻击
- secure:为true,则只在 https 的安全协议下传输
- cookie 存放在浏览器中,容易被篡改,需要验证合法性
- cookie 里不应该存放密码等敏感数据
- cookie 不能跨域传输
- 一个浏览器对于一个网站最多存 20 个 cookie,一个浏览器一般 300 个
session
- session 存放在服务端,不容易被篡改
- 服务端通过 sessionId 存放 session 的值,sessionId 的值是一个不重复且不容易找到规律被仿制的字符串
- sessionId 一般会被放在 cookie 中传递给浏览器
- 如果在线的人数较多,session 容易给服务端造成压力,要定期清理
- session 一般依赖 cookie,但移动端对 cookie 支持不太好,所以移动端一般使用 token
- 服务端采用集群部署的时候,需要考虑多个机器之间 session 共享的问题,因为处理用户请求的机器不一定是创建 session 的那一台
Token
服务端将用户信息经过 Base64Url 编码过后传给在客户端,之后的每次请求都带上 token 给到服务端校验。服务端使用一个加密算法+一个秘钥对数据进行签名,收到 token 后再解密进行验证。
1. 用户通过用户名和密码发送请求。
2. 程序验证。
3. 程序返回一个签名的 token 给客户端。
4. 客户端储存 token,并且每次用于每次发送请求。
5. 服务端验证 token 并返回数据。
- token 由应用管理,不受同源策略影响
- token 不使用 cookie,所以比较安全,避免 CSRF 攻击
- 移动端适用
CSRF & XSS
CSRF:跨站请求伪造
- 方式:拿着用户的 cookie 去骗取浏览器的信任。攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令
- 防御手段:
- Token
- 验证码
- referer 验证
XSS:跨站脚本攻击
- 方式:执行一些非法的脚本,来盗取信息或执行某些操作
- 类型:
- 反射型:用户输入的数据从服务器反射给用户浏览器,要利用这个漏洞,攻击者必须以某种方式诱导用户访问一个精心设计的URL(恶意链接),才能实施攻击
- 存储型:与反射型的根源类似,不同的是恶意代码会被保存在服务器中,导致其它用户(前端)和管理员(前后端)在访问资源时执行了恶意代码
- DOM 型:通过操作 DOM 执行一些恶意代码
- 防御手段:
- httpOnly 防止窃取 cookie
- 用户和服务端的输入输出检查