11

浏览器下的一次网络请求流程

以输入 http://github.com/appleguardu... web 请求本质上就是一台主机去另一台主机获取对应的资源.

1.进程切换

当导航栏输入 http://github.com/appleguardu... 浏览器进程把这个url交给网络进程处理, 给我去这台主机下取资源;

2.解析请求的URL,DNS查询IP,准备端口

  • URL: 统一资源定位符
protocol: 协议头, 譬如有http,ftp等
host: 主机域名或IP地址
port: 端口号
path: 目录路径
query: 即查询参数
fragment: 即#后的hash值,一般用来定位到某个位置
  • DNS(域名与IP的映射系统)
由于不同主机之间都是通过IP地址来确定的,所以首先会进行域名解析,查询; 
浏览器会向域名服务器去查询http://github.com对应的Ip.

//  Ip查询也有一套优化机制: 缓存机制(可以减少网络请求)
浏览器缓存:   浏览器会按照一定的频率缓存 DNS 记录;
操作系统缓存: 如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统中找;
路由缓存:     路由器也有 DNS 缓存;
ISP(互联网服务提供商)的DNS服务器: ISP 有专门的 DNS 服务器应对 DNS 查询请求;
根服务器: ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询;
(DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址,然后再问.github 域名服务器,依次类推)
  • 端口port
如果不是特别指定,http协议默认的80端口, https: 443

3.等待 TCP 队列(浏览器对tcp的限制)

chorome浏览器有个机制, 统一域名下同时最多只能建立 6 个 TCP连接(浏览器的限制可能不一样), 如果超过 6 个请求, 那么剩下的会进入排队等待的状态, 等待前面的完事后自己再去建立连接

4.建立 TCP 连接

TCP 协议

TCP运行: 连接创建、数据传送和连接终止
  • 连接创建: 三次握手(保证可靠传输)
1.客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分;
客户端把这段连接的序号设定为随机数A。

2.服务器端应当为一个合法的SYN回送一个SYN/ACK。
ACK的确认码应为A+1,SYN/ACK包本身又有一个随机产生的序号B。

3.最后,客户端再发送一个ACK。此时包的序号被设定为A+1,而ACK的确认码则为B+1。
当服务端收到这个ACK的时候,就完成了三次握手,并进入了连接创建状态。

client侧: SYN         ACK
server侧:     SYN+ACK
  • 数据传输(一些机制)
使用序号,对收到的TCP报文段进行排序以及检测重复的数据;
使用校验和检测报文段的错误,即无错传输;
使用确认和计时器来检测和纠正丢包或延时;
流量控制(Flow control);拥塞控制(Congestion control);丢失包的重传。
  • 连接终止(四次挥手)
连接终止使用了四路握手过程(或称四次握手,four-way handshake),在这个过程中连接的每一侧都独立地被终止。
当一个端点要停止它这一侧的连接,就向对侧发送FIN,对侧回复ACK表示确认。
因此,拆掉一侧的连接过程需要一对FIN和ACK,分别由两侧端点发出。

client侧: FIN          ACK
server侧:     ACK  FIN

5.发送 HTTP 请求

当 TCP 连接建立之后, 浏览器与服务器之间开始通信,传输数据

构建请求报文

  • 请求行
GET /appleguardu HTTP/1.1
// 请求方式:(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE
// 资源路径URI: /appleguardu
// 协议版本: HTTP 1.1
  • 请求头(常用的)
Accept: 接收类型,表示浏览器支持的MIME类型(对标服务端返回的Content-Type)
Accept-Encoding: 浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type: 客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since: 对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中
Expires: 缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间
Max-age: 代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中
If-None-Match: 对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中
Cookie: 有cookie并且同域访问时会自动带上
Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host: 请求的服务器URL
Origin: 最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私
Referer: 该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)
User-Agent: 用户客户端的一些必要信息,如UA头部等
  • 请求体
post 请求中参数常放在请求体中
如参数的序列化形式(a=1&b=2这种;
或者直接放表单对象(Form Data对象,上传时可以夹杂参数以及文件的等
注意: 如果请求的是缓存资源,则会终止请求

浏览器会对请求的文件资源进行检查, 如果这个请求的资源存在于浏览器缓存当中, 请求就会被拦截, 浏览器直接从本地缓存中获取后发给网络进程, 并结束此处请求过程
关于http缓存介绍

缓存分类

缓存可以简单的划分成两种类型: 强缓存(200 from cache)与协商缓存(服务端返回304);
强缓存: 浏览器如果判断本地缓存未过期,就直接使用,无需发起http请求;
协商缓存304: 当浏览器再次向服务端发起http请求,然后服务端(304)告诉浏览器文件未改变,让浏览器使用本地缓存

  • 如何区分两类缓存? 通过设置 http 头部信息
# 几种头部缓存属性
强缓存(http1.1): Cache-Control/Max-Age 
强缓存(http1.0): Pragma/Expires 

协商缓存(http1.1): If-None-Match/E-tag
协商缓存(http1.0): If-Modified-Since/Last-Modified

<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
HTML页面中也有一个meta标签可以控制缓存方案-Pragma,不过一般都是服务端设置
  • http1.0下的缓存控制
Pragma: 严格来说,它不属于专门的缓存控制头部,但是它设置no-cache时可以让本地强缓存失效(属于编译控制,来实现特定的指令,主要是因为兼容http1.0,所以以前又被大量应用)

Expires: 服务端配置的,属于强缓存,用来控制在规定的时间之前,浏览器不会发出请求,而是直接使用本地缓存,
注意: Expires一般对应服务器端时间,如Expires: Fri, 30 Oct 1998 14:19:41
如果客户端时间和服务端不同步,可能造成浏览器本地的缓存无用或者一直无法过期

If-Modified-Since/Last-Modified: 这两个是成对出现的,属于协商缓存的内容;
其中浏览器的头部是If-Modified-Since,而服务端的是Last-Modified;
它的作用是,在发起请求时,如果If-Modified-Since和Last-Modified匹配,
那么代表服务器资源并未改变,因此服务端不会返回资源实体,而是只返回头部,通知浏览器可以使用本地缓存...
Last-Modified,顾名思义,指的是文件最后的修改时间,而且只能精确到1s以内
注意: 如果服务端的文件会周期性的改变, 导致缓存失效
  • http1.1下的缓存控制
Cache-Control: 缓存控制头部,有no-cache、max-age等多种取值

Max-Age: 服务端配置的,用来控制强缓存,在规定的时间之内,浏览器无需发出请求,直接使用本地缓存,
注意,Max-Age是Cache-Control头部的值,不是独立的头部,
譬如Cache-Control: max-age=3600,而且它值得是绝对时间,由浏览器自己计算

If-None-Match/E-tag: 这两个是成对出现的,属于协商缓存的内容;
其中浏览器的头部是If-None-Match,而服务端的是E-tag,
同样,发出请求后,如果If-None-Match和E-tag匹配,则代表内容未变,通知浏览器使用本地缓存;
和Last-Modified不同,E-tag更精确,它是类似于指纹一样的东西,所以会优先级也更高...
基于FileEtag INode Mtime Size生成,也就是说,只要文件变,指纹就会变,而且没有1s精确度的限制。

6.服务器处理 http 请求

当服务器接收到浏览器的请求报文后, 会交给对应的处理程序进行处理(此处省略...),然后想服务器返回响应报文

响应报文

  • 响应行
HTTP/1.1 200 OK
// 协议版本 状态码 状态码信息
// 200——表明该请求被成功地完成,所请求的资源发送回客户端
// 304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存
// 400——客户端请求有错(譬如可以是安全模块拦截)
// 401——请求未经授权
// 403——禁止访问(譬如可以是未登录时禁止)
// 404——资源未找到
// 500——服务器内部错误
// 503——服务不可用
  • 响应头(常用的)
Access-Control-Allow-Headers: 服务器端允许的请求Headers
Access-Control-Allow-Methods: 服务器端允许的请求方法
Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*)
Content-Type: 服务端返回的实体内容的类型
Date: 数据从服务器发送的时间
Cache-Control: 告诉浏览器或其他客户,什么环境可以安全的缓存文档
Last-Modified: 请求资源的最后修改时间
Expires: 应该在什么时候认为文档已经过期,从而不再缓存它
Max-age: 客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效
ETag: 请求变量的实体标签的当前值
Set-Cookie: 设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端
Keep-Alive: 如果客户端有keep-alive,服务端也会有响应(如timeout=38)
Server: 服务器的一些相关信息
  • 响应体
响应体一般是服务端需要传给客户端的内容;
如接口请求时: 实体中就是对应的信息的 json格式对象
如页面请求时: 实体中就是对应的 html 字符串
注意

一般来说, 请求头部和响应头部是互相匹配的
如请求头部的Accept要和响应头部的Content-Type匹配,否则会报错;
跨域请求时,请求头部的Origin要匹配响应头部的Access-Control-Allow-Origin,否则会报跨域错误
还有在使用缓存时,
请求头部的If-Modified-Since、If-None-Match分别和
响应头部的Last-Modified、ETag相对应

以上基本都是通过服务端程序来控制的

重定向

当响应状态码是 301 时,会发生重定向, 浏览器进程重新让导航进行工作, 修改 location 字段中的地址,继续上述的请求流程

8.浏览器解析并渲染页面

浏览器进程开始调度, 渲染进程通过管道接受网络进程的html数据,开始解析html工作

渲染进程工作流

  • 1.DOM Tree 构建
当渲染进程接收到导航的确认信息,开始接受HTML数据时,主线程会解析文本字符串为 DOM;
这里依靠 HTMl 解析器: 
接受字节流 -> 维护 token 栈 -> 生成节点node -> 组成 DOM;

遇到内嵌 script 时, DOM解析工作停止; js引擎介入执行(可能会修改dom结构);
执行完 js 后恢复解析工作, 所以 js 会阻塞 dom 解析.

遇到其他内联资源时(css,img)会通知网络进程去下载, 特别是 css;
js 在操作dom 样式时会依赖cssom,生成 layoutTree也需要 cssom; 
所以 css 又会阻塞 js 的执行
  • 2.样式计算, 构建cssom(css规则树)
这里会基于 CSS 选择器解析 CSS 获取每一个节点的最终的计算样式值;
对应的就是styleSheets
  • 3.计算布局, 生成layout tree
想要渲染一个完整的页面,除了获知每个节点的具体样式,还需要获知每一个节点在页面上的位置,
布局其实是找到所有元素的几何关系的过程。

这里通过遍历 DOM 及相关元素的计算样式,主线程会构建出包含每个元素的坐标信息及盒子大小的布局树。
布局树和 DOM 树类似,但是其中只包含页面可见的元素,如果一个元素设置了 `display:none` ,
这个元素不会出现在布局树上,伪元素虽然在 DOM 树上不可见,但是在布局树上是可见的。
  • 4.分层,绘制(layer -> paint)
为特定的节点生成专用图层(will-change属性), 生成 图层树;
为图层生成绘制表(记录了绘制指令和顺序), 提交到合成线程
  • 5.分块,光栅化
合成线程将图层分为图块, 通过光栅化生成位图(GPU 进程)
  • 6.合成,显示
图块被光栅化后会生成一个绘制命令, 通过 IPC 提交给浏览器进程去执行,
绘制到内存中然后展示在显示器上

9.断开TCP连接

当数据传送完毕,需要断开 tcp 连接,此时发起 tcp 四次挥手;
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。
不过如果浏览器或者服务器在其头信息中加入了

Connection: Keep-Alive (http/1.1下默认启用, http/1.0默认close)
// keep-alive不会永远保持,它有一个持续时间,一般在服务器中配置(如apache,
另外长连接需要客户端和服务器都支持时才有效

TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接 发送请求。
保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。 比如,一个 Web 页面中内嵌的图片就都来自同一个 Web 站点,如果初始化了一个持久连 接,你就可以复用该连接,以请求其他资源,而不需要重新再建立新的 TCP 连接。

参考

TCP 协议百科
HTTP headers

  • http2.0 了解
- 多路复用(即一个tcp/ip连接可以请求多个资源)
- 首部压缩(http头部压缩,减少体积)
- 二进制分帧(在应用层跟传送层之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
- 服务器端推送(服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端)
- 请求优先级(如果流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求。)

a_dodo
2.4k 声望1k 粉丝

天下事有难易乎?