经常会有各种文章啥的都有提到从浏览器地址栏输入 URL 到浏览器渲染出页面的过程是怎样的。

之前我的认识是:DNS 解析,请求静态资源,渲染页面。

然而,事实上我还是太不专业了,看到的都只是一些表象,从表象往专业看,其实这里蕴含了多个方向的前端知识,下面就来谈谈个人的一些拙见。

一、从输入 URL 到 DNS 解析

浏览器每开一个窗口,就会新建一个进程,这个可以打开 chrome 的 task manager 或者 win 的任务管理器自行验证。在浏览器地址栏输入一个网址,比如 https://canace.site:8080/a?name=b, 回车, 浏览器会去解析这个 URL,这个例子解析出来的结构可能是这样的

protocol: "https:"
host: "canace.site"
port: "8080"
path: "/a"
query: {name: a}

拿到 URL 的解析之后,浏览器内核进程根据 url 解析出的协议和域名,开一个网络请求线程。

DNS 解析

完成以上步骤后,通过 DNS 查询去找出”canace.site”对应的 ip 地址, DNS 的查找顺序一般是: 浏览器缓存(chrome://net-internals/#DNS) => 操作系统缓存 => 本地host文件 -> 路由器缓存 => ISP(网络运营商,比如电信) DNS缓存 => 顶级DNS服务器/根DNS服务器, 可以看出 DNS 寻址还是会花点时间的,所以在一个页面中还是用少一点域名比较好。

这里有个比较有意思的应用,就是有的时候手机上能用到 wifi,但是电脑不行,网上的教程一般是建议改 DNS 为: 114.114.114.114 或者 8.8.8.8,114.114.114.114 这个地址是国内移动、电信和联通通用的DNS,8.8.8.8 是谷歌提供的全球通用的 DNS,修改本地 DNS 为这两个地址,对于使用 DNS 引导内容分发的网站,解析成功率相对来说更高, 而且速度相对快且稳定。

既然 DNS 可以被修改,那么必然会存在安全隐患,这里就要讲到 DNS 劫持了,这里需要思考 DNS 劫持是什么,DNS 劫持是怎么实现的,DNS 劫持有哪几种等,下面来理一下:

DNS 劫持是什么

DNS 劫持即通过某种技术手段,篡改正确域名和IP地址的映射关系,使得域名映射到了错误的IP地址,因此可以认为DNS劫持是一种DNS重定向攻击

DNS 劫持的分类

按照寻址路径,DNS 劫持可以分为以下几类:

(1)本地DNS劫持

  • 黑客通过木马病毒或者恶意程序入侵电脑,串改 DNS 配置(hosts 文件, DNS 服务器地址或者 DNS 缓存等)

  • 黑客利用路由器漏洞或者破解路由器管理账号入侵路由器并篡改 DNS 配置

  • 一些企业代理设备针对企业内部场景对特定的域名做 DNS 劫持并解析为指定的结果

(2)DNS解析路径劫持

发生在客户端和服务端网络通信阶段

  • DNS 请求转发, 通过技术手段(中间盒子,软件等)将DNS流量重定向到其他DNS服务器

  • DNS 请求复制, 利用分光等设备将DNS查询复制到网络设备,并先于正常应答返回DNS劫持的结果

  • DNS 请求代答, 网络设备或者软件直接代替DNS服务器对DNS查询进行应答

(3)篡改DNS服务器记录

黑客非法入侵DNS服务器记录管理账号,直接修改DNS记录的行为

上面提到了进程和线程,就顺带说一下这两个经常被搞混的难兄难弟。

进程 VS 线程

(1) 进程是 cpu 资源分配的最小单位,系统会为每一个进程分配一定的内存

(2) 进程之间相互独立

(3) 线程是 cpu 调度的最小单位

(4) 一个进程由一个或多个线程组成

(5) 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

浏览器进程/线程模型

浏览器是多进程的,每打开一个标签页会创建一个新的进程(有时会进行进程合并)

浏览器的主要进程包括以下几种:

(1) Browser进程, 浏览器的主进程(负责协调、主控),只有一个, Browser进程的作用有以下几个:

  • 负责浏览器界面显示,与用户交互。如前进,后退等

  • 负责各个页面的管理,创建和销毁其他进程

  • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上

  • 网络资源的管理,下载等

Browser进程的主要线程有以下几种:

  • UI线程: 控制浏览器上的按钮及输入框

  • 网络线程: 处理网络请求,从网上获取数据

  • storage线程: 控制文件等的访问

(2) 浏览器渲染进程, 亦被称为浏览器内核进程, Renderer进程, 内部是多线程的, Renderer 进程默认每个 Tab 页面一个进程,互不影响,主要用于页面渲染,脚本执行,事件处理等,Renderer 进程包括的主要线程如下:

(3) GUI渲染线程

  • 负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等。

  • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

  • GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到JS 引擎空闲时立即被执行。

(4) JS 引擎线程

  • 也称为JS内核,负责处理 Javascript 脚本程序(例如V8引擎)

  • JS 引擎线程负责解析 Javascript 脚本,运行代码

  • JS 引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个 JS 线程在运行JS程序

  • GUI 渲染线程与 JS 引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
    需要大量计算的时候,可以创建一个 Web Worker,向浏览器申请多开一个线程, 专门用于计算

(5) 事件触发线程

  • 归属于浏览器而不是 JS 引擎,用来控制事件循环

  • 当 JS 引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中

  • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理

  • 由于 JS 的单线程关系,所以这些待处理队列中的事件都得排队等待 JS 引擎处理(当 JS 引擎空闲时才会去执行)

(6) 定时触发器线程

  • 传说中的 setInterval 与 setTimeout 所在线程

  • 浏览器定时计数器并不是由 JavaScript 引擎计数的,(因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待 JS 引擎空闲后执行)

  • W3C 在 HTML 标准中规定,规定要求 setTimeout 中低于 4ms 的时间间隔算为 4ms

(7) 异步 http 请求线程

  • XMLHttpRequest 在连接后是通过浏览器新开一个网络线程请求

  • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由 JavaScript 引擎执行。

(8) Subframe 进程

iframe 的渲染 — Site Isolation, Site Isolation 机制从 Chrome 67 开始默认启用,这种机制允许在同一个 Tab 下的跨站 iframe 使用单独的进程来渲染

二、网络连接建立到获取内容断开

DNS 解析完成之后,会向对应 ip 的服务器发起请求,从发起请求到请求完毕经历了三次握手、客户端与服务端 http 交互以及四次挥手,下面来具体讲讲这三个阶段都发生了什么

1、三次握手建立连接

(1) 三次握手的作用

  • 明确客户端和服务端的收、发能力是否正常

  • 指定服务端和客户端的初始化序列号为后面的可靠性传送做准备

(2) 三次握手的过程

  • 客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_SEND 状态

  • 服务端发送自己的 SYN 报文作为应答,同样指明自己的ISN(s)。为了确认客户端的 SYN,将 ISN(c)+1 作为 ACK 数值。这样,每发送一个SYN,序列号就会加 1, 如果有丢失的情况,则会重传, 此时服务器处于 SYN_RCVD 的状态

  • 为了确认服务器端的 SYN,客户端将 ISN(s)+1 作为返回的 ACK 数值, 此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,双方建立起连接成功

(3) SYN 攻击

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到 SYN 洪泛攻击。

SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

2、客户端与服务端 http 交互

前后端的交互用的一般是 http 协议, 经常会涉及到的内容包括 http 报文头部信息, 状态码, 缓存, 跨域等

(1) http 报文结构

随便打开一个网页查看网络请求,都会发现有三个一样的字段: General、Response Header 以及 Request Header, 在网页调试中,我们也经常会去看这三个地方

General(通用头部)

在获取网页内容时,会把网页文件的地址和请求方法放到 http 报文头部,请求完之后,控制台一般会有以下的一些信息

Request Url: 请求的web服务器地址

Request Method: 请求方式
(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)

Status Code: 请求的返回状态码,如200代表成功

Remote Address: 请求的远程服务器地址(会转为IP)

上面是对每个字段的描述,其中 method 按照协议可以分为

  • HTTP1.0 method: GET, POST 和 HEAD

  • HTTP1.1 method: GET、POST、HEAD、OPTIONS, PUT, DELETE, TRACE 和 CONNECT

常用状态码分类:

  • 1xx——指示信息,表示请求已接收,继续处理

  • 2xx——成功,表示请求已被成功接收、理解、接受

  • 3xx——重定向,要完成请求必须进行更进一步的操作

  • 4xx——客户端错误,请求有语法错误或请求无法实现

  • 5xx——服务器端错误,服务器未能实现合法的请求

常用状态码:

  • 200——表明该请求被成功地完成,所请求的资源发送回客户端

  • 304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存

  • 400——客户端请求有错(譬如可以是安全模块拦截)

  • 401——请求未经授权

  • 403——禁止访问(譬如可以是未登录时禁止)

  • 404——资源未找到

  • 500——服务器内部错误

  • 503——服务不可用

Request Header(请求头部)

常用请求头部

Accept: 接收类型,表示浏览器支持的MIME类型
(对标服务端返回的Content-Type)

Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收

Content-Type:客户端发送出去实体内容的类型

缓存相关
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since(http1.0):对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内
Expires(http1.0):缓存控制,在这个时间内不会请求,直接使用缓存,而且是服务端时间
Max-age(http1.1):代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存
If-None-Match(http1.1):对应服务端的ETag,用来匹配文件内容是否改变(非常精确)

Cookie: 有cookie并且同域访问时会自动带上

Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive

Host:请求的服务器URL

Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私

Referer:该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)

User-Agent:用户客户端的一些必要信息,如UA头部等

Response Header(响应头部)

常用响应头部

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:服务器的一些相关信息

跨域请求时,请求头部的Origin要匹配响应头部的Access-Control-Allow-Origin,否则会报跨域错误

在使用缓存时,请求头部的If-Modified-Since、If-None-Match分别和响应头部的Last-Modified、ETag对应

写的有点累,得空再写了

未完待续……

参考文献:

聊一聊DNS劫持那些事

从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!

图解浏览器的基本工作原理

常用dns114.114.114.114与8.8.8.8的区别

“三次握手,四次挥手”你真的懂吗?

面试官,不要再问我三次握手和四次挥手