从输入URL到页面展示, 浏览器在这中间到底干了什么? -- 浏览器系列(2)

422 阅读8分钟

从用户输入URL到页面被展示出来,浏览器在这中间到底干了什么?”这是一个常常被问起的问题,其中会牵扯到浏览器各个进程之间的协作,这是一个包含很多细节的问题,本节我们就来探究一下这个过程。

这一过程也被称之为导航(navigation

下图是导航流程的示意图:

navigation

从零开始的导航流程

1. 处理输入

当用户在地址栏输入一些查询关键字后,浏览器主进程会判断用户输入的内容是搜索内容还是请求的URL

  • 如果是搜索内容,则会使用浏览器的默认搜索引擎合成新的带搜索内容的URL。如在地址栏输入关键字“浏览器导航流程”,且默认搜索引擎是google,合成的新URL将是https://www.google.com/search?q=浏览器导航流程
  • 如果判断内容符合URL规则,那么会根据规则进一步补全,如补全协议;合成完整的URL。如输入的是google.com,最终会合成为https://www.google.com/

input url

2. 开始导航

在用户完成输入搜索内容按下确认键后,浏览器主进程会通过IPC将合成URL发给网络进程,并且当前的标签页开始显示loading状态。

start loading

3. 发起 HTTP 请求

网络进程在接收到URL请求后,会先查询本地是否缓存了该资源。如果有,则直接返回资源副本给浏览器主进程,无需进行网络请求,否则直接进入网络请求流程,网络进程会进行一系列的诸如DNS寻址等操作来初始化一个网络请求获取站点内容,如果收到服务器返回的重定向的http code如301302,网络进程会告诉浏览器主进程URL发生了重定向,新的URL网络请求将被重新发送,一切又重头开始。

这里不展开网络请求流程的细节,详情请查看后续HTTP 请求流程

4. 解析响应数据

当网络进程收到响应数据后,网络进程会开始解析响应数据类型,判断它到底是HTML页面,还是下载资源,网络进程会查看响应头中的Content-Type字段,该字段用于告诉浏览器响应体的数据类型,如果Content-Typetext/html,则说明返回数据是HTML格式,如果是application/octet-stream,则说明是字节流类型(更多类型详见媒体类型)。但有些时候该字段可能会缺失或者设置不正确,网络进程还会做媒体类型嗅探(MIME type sniffing),以获取正确的媒体类型。

如果最终Content-Type是字节类或者其他下载类型,意味着该数据需要被移交给下载管理器处理,导航流程结束。如果是HTML文件,则会继续导航流程。

网络进程还会对HTML文件进行安全检查,和CORB(Cross Origin Read Blocking)检查,保证敏感的跨域数据不会被发送给渲染进程

5. 准备渲染进程

当网络进程结束所有的检查工作后,它会向浏览器主进程发起消息,告诉浏览器主进程数据已准备好, 浏览器主进程会准备一个渲染进程来渲染界面。

在这里,浏览器做了一个优化工作。因为网络请求可能需要几百毫秒甚至几秒的时间才能完成,为了缩短导航需要的时间,在第二步发URL发送给网络进程后,浏览器已经知道了所要导航的站点,所以在网络进程干活的时候,浏览器主进程就会开始准备渲染进程。如果一切顺利,没有重定向,那么在网络进程拿到数据后,渲染进程也已经准备好了。这样就可以缩短准备渲染进程的时间。但如果发生了重定向,那么之前准备好的渲染进程就会被摒弃,会重新准备一个新的渲染进程。

6. 提交(commit)导航

到现在,数据和渲染进程都已经准备好了,浏览器主进程会通过IPC告诉渲染进程提交本次的导航(commit navigation)。

渲染进程在收到提交文档的消息后,会和网络进程建立传输数据的管道用于数据传输,当文档数据传输完成后,渲染进程会返回确认提交的消息给浏览器主进程。

一旦浏览器接收到确认提交的消息,就会更新浏览器界面状态,包括安全状态,地址栏URL,前进后退的历史状态。此时整个导航过程就结束了,文档加载阶段开始。

当渲染进程完成页面渲染后(页面上的所有iframes的onload事件都被触发,且所有对应的回调函数都已经执行完成时),它会通过IPC告诉浏览器主进程,然后浏览器主进程会关闭标签页上的loading状态。

以上就是一次简单导航的全部流程。

导航到其他站点

在完成简单导航之后,如果此时用户在标签页的导航栏中输入了其他的URL又会发生什么呢?浏览器会重新执行一遍之前的六个步骤。不过在那之前,它需要通知当前渲染进程做一些收尾工作。具体就是处理beforeunload事件,beforeunload事件可以让用户在重新导航或者关闭该页面时,给用户展示一个”你确认要离开当前页面吗?“的二次确认框。因此用户通过beforeunload事件取消导航,让浏览器不再执行后续任何操作。

close page

重新导航的行为也可能是通过页面内发起,如代码window.location=newsite.com被执行。在这种情况下,渲染进程会先检查是否注册了beforeunload事件,然后再执行后续步骤。和之前唯一的区别在于这次的导航请求是由渲染进程向浏览器主进程发起的。

如果重新导航到不同站点,会有另一个渲染进程被启动来完成这次重新导航,而当前渲染进程会继续处理现有页面的收尾工作。

HTTP 请求流程

这里我们具体来讲讲网络进程在发送网络请求的流程,下图是网络请求流程示意图:

network process 以URLhttp://my-site.com/index.html为例:

1. 构建请求

首先,浏览器会构建请求行信息:

GET /index.html HTTP1.1

2. 查找缓存

在真正发起网络请求前,浏览器会先从缓存中查询是否有该资源副本。当浏览器发现请求资源已经在浏览器缓存中存有副本,它会拦截请求,直接返回该资源,如果缓存查找失败,就会进入网络请求。

合理运用缓存可以缓解服务端压力,并且可以实现资源的快速加载

3. 准备IP地址和端口

HTTP协议作为应用层协议,它的内容需要通过TCP/IP作为传输层协议来发送。所以在发送HTTP请求前,首先需要进行TCP连接,而TCP连接需要知道IP地址和端口。

所以第一步,浏览器会请求DNS(Domain Name System)返回域名对应的IP地址,当然,浏览器也提供了DNS数据缓存服务,如果某个域名已经解析过了,浏览器会缓存该结果,以供下次查询时直接使用。

DNS query 接下来就是获取端口号,通常情况下,如果URL没有特别指明端口号,那么HTTP协议默认端口是80。

4. 等待TCP对列

在使用HTTP1.1协议时,chrome有个机制:同一域名同时最多只能建立6个TCP连接,如果同一域名下同时有10个请求发生,那么其中4个会进入等待状态,直至进行中的请求完成再开始。如果当前请求数量小于6个,则直接进行TCP连接。

HTTP2则没有该限制

5. 建立TCP连接

这个阶段通过三次握手建立客户端和服务端之间的连接。

6. 发送HTTP请求

TCP连接建立后,浏览器就可以向服务端发送数据。发送的数据包括请求行,请求头,请求体。这里不展开HTTP协议内容。

http request

7. 服务端处理HTTP请求

服务端接收到HTTP请求后,根据请求内容进行相应后端逻辑处理。

8. 返回请求结果

服务端在完成逻辑处理后,把结果数据返回给前端。返回结果数据的格式和之前请求数据的格式非常相似。

http response

9. 断开TCP连接

通常情况下,在客户端接收到服务器的响应数据后,就会通过四次挥手关闭TCP连接。

但如果在浏览器请求或者服务器响应数据的头部信息中加入了Connection: Keep-Alive,那么TCP连接在接收完数据后依旧保持打开状态,这样浏览器就可以继续通过同一个连接发送请求。

保持 TCP 的连接可以省去下次请求时建立连接的时间,提升资源加载速度

如果对本文有什么意见和建议,欢迎讨论和指正!