从我们输入 URL 并按下回车键到看到网页结果之间发生了什么? 换句话说, 一张网页, 要经历怎样的过程, 才能抵达用户面前? 下面来从一些细节上面尝试一下探寻里面的秘密
前言: 键盘与硬件中断
说到输入 URL, 当然是从手敲键盘开始对于键盘, 生活中用到的最常见的键盘有两种: 薄膜键盘机械键盘
薄膜键盘: 由面板上电路隔离层下电路构成有外观优美寿命较长成本低廉的特点, 是最为流行的键盘种类键盘中有一整张双层胶膜, 通过胶膜提供按键的回弹力, 利用薄膜被按下时按键处碳心于线路的接触来控制按键触发
机械键盘: 由键帽机械轴组成键盘打击感较强, 常见于游戏发烧友与打字爱好者每一个按键都有一个独立的机械触点开关, 利用柱型弹簧提供按键的回弹力, 用金属接触触点来控制按键的触发
(机械键盘独立触点原理图)
键盘传输信号到操作系统后便会触发硬件中断处理程序硬件中断是操作系统中提高系统效率满足实时需求的非常重要的信号处理机制, 它是一个异步信号, 并提供相关中断的注册表 (IDT) 与请求线 (IRQ) 键盘被按压时, 将通过请求线将信号输入给操作系统, CPU 在当前指令结束之后, 根据注册表与信号响应该中断并利用段寄存器装入中断程序入口地址具体可参看操作系统与汇编相关书籍
当然本文主要不是介绍硬件与操作系统中的细节, 前言只是想说明, 从输入 URL 到浏览器展现结果页面之间有太多底层相关的知识, 怀着一颗敬畏的心并且在有限的篇幅中是无法详细阐述的, 所以本文会将关注点放在一个稍高的角度上来看, 从浏览器替我们发送请求之后到看到页面显示完成这中间中发生了什么事情, 比如 DNS 解析浏览器渲染
下图是从用户发送一条请求开始到最终看到页面的流程简化图, 本文将以该请求为线索, 在下面一步一步探索其中的细节
浏览器解析 URL
按下回车键之前
比如我按下一个 b 键, 会出现很多待选 URL 给我, 第一个便是百度那么其实是在浏览器接收到这个消息之后, 会触发浏览器的自动完成机制, 会在你之前访问过的搜索最匹配的相关 URL, 会根据特定的算法, 甚至涉及到机器学习来分析出你有可能想搜索的关键字, 显示出来供用户选择
按下回车键之后
依据上述键盘触发原理, 一个专用于回车键的电流回路通过不同的方式闭合了然后触发硬件中断, 随之操作系统内核去处理对应中断省略其中的过程, 最后交给了浏览器这样一个回车信号那么浏览器 (本文涉及到的浏览器版本为 Chrome 61) 会进行以下但不仅限于以下炫酷 (乱七八糟) 的步骤:
解析 URL: 您输入的是 http 还是 https 开头的网络资源 / file 开头的文件资源 / 待搜索的关键字? 然后浏览器进行对应的资源加载进程
URL 转码: RFC 标准中规定部分字符可以不经过转码直接用于 URL, 但是汉字不在范围内所以如果在网址路径中包含汉字将会被转码, 比如 https://zh.wikipedia.org/wiki/HTTP 严格传输安全转换成 https://zh.wikipedia.org/wiki/HTTP严格传输安全
HSTS: 鉴于 HTTPS 遗留的安全隐患, 大部分现代浏览器已经支持 HSTS 对于浏览器来说, 浏览器会检测是否该网络资源存在于预设定的只使用 HTTPS 的网站列表, 或者是否保存过以前访问过的只能使用 HTTPS 的网站记录, 如果是, 浏览器将强行使用 HTTPS 方式访问该网站
DNS 解析
不查 DNS, 读取缓存
浏览器中的缓存: 对于 Chrome, 缓存查看地址为: chrome://net-internals/#dns
本地 hosts 文件: 以 Mac 与 Linux 为例, hosts 文件所在路径为:/etc/hosts 所以有一种翻墙的方式就是修改 hosts 文件避免 GFW 对 DNS 解析的干扰, 直接访问真正 IP 地址, 但已经不能完全生效因为 GFW 还有根据 IP 过滤的机制
发送 DNS 查找请求
DNS 的查询方式是: 按根域名 ->顶级域名 ->次级域名 ->主机名这样的方式来查找的, 对于某个 URL, 如下所示
查询步骤为:
查询本地 DNS 服务器本地 DNS 服务器地址为连接网络时路由器指定的 DNS 地址, 一般为 DHCP 自动分配的路由器地址, 保存在 / etc/resolv.conf, 而路由器的 DNS 转发器将请求转发到上层 ISP 的 DNS, 所以此处本地 DNS 服务器是局域网或者运营商的
从根域名服务器查到顶级域名服务器的 NS 记录和 A 记录 (IP 地址) 世界上一共有十三组根域名服务器, 从 A.ROOT-SERVERS.NET 一直到 M.ROOT-SERVERS.NET, 由于已经将这些根域名服务器的 IP 地址存放在本地 DNS 服务器中
从顶级域名服务器查到次级域名服务器的 NS 记录和 A 记录(IP 地址)
从次级域名服务器查出主机名的 IP 地址
DNS 解析图示
以 www.google.com 为例, 下面是在阿里云实例上作的完整的 DNS 查询过程:
由于本次测试是在阿里云上的实例进行测试, 所以首先从 100.100.2.138 这个阿里内网 DNS 服务器查找到所有根域名服务器的映射关系
访问根域名服务器(f.root-servers.net), 拿到 com 顶级域名服务器的 NS 记录与 IP 地址
访问顶级域名服务器(e.gtld-servers.net), 拿到 google.com 次级域名服务器的 NS 记录与 IP 地址
访问次级域名服务器(ns2.google.com), 拿到 www.google.com 的 IP 地址
所以总的来说, DNS 的解析是一个逐步缩小范围的查找过程
建立 HTTPSTCP 连接
确定发送目标
拿到 IP 之后, 还需要拿到那台服务器的 MAC 地址才行, 在以太网协议中规定, 同一局域网中的一台主机要和另一台主机进行直接通信, 必须要知道目标主机的 MAC 地址所以根据 ARP(根据 IP 地址获取物理地址的一个 TCP/IP 协议)获取到 MAC 地址之后保存到本地 ARP 缓存之后与目标主机准备开始通信具体细节参见维基百科 DHCH/ARP
建立 TCP 连接
为什么握手一定要是三次?
TCP 三次握手
第一次与第二次握手完成意味着: A 能发送请求到 B, 并且 B 能解析 A 的请求
第二次与第三次握手完成意味着: A 能解析 B 的请求, 并且 B 能发送请求到 A
这样就保证了 A 与 B 之间既能相互发送请求也能相互接收解析请求同时避免了因为网络延迟产生的重复连接问题, 比如 A 发送一次连接请求但网络延迟导致这次请求是在 A 重发连接请求并完成与 B 通信之后的, 有三次握手的话, B 返回的建立请求 A 就不会理睬了
短连接与长连接?
短连接过程演示
上图是一个短连接的过程演示, 对于长连接, A 与 B 完成一次读写之后, 它们之间的连接并不会主动关闭, 后续的读写操作会继续使用这个连接另外, 由于长连接的实现比较困难, 需要要求长连接在没有数据通信时, 定时发送数据包(心跳), 以维持连接状态, 并且长连接对于服务器的压力也会很大, 所以推送服务对于一般的开发者是非常难以实现的, 这样的话就出现了很多不同的大型厂商提供的消息推送服务
进行 TLS 加密过程
Hello 握手开始于客户端发送 Hello 消息包含服务端为了通过 SSL 连接到客户端的所有信息, 包括客户端支持的各种密码套件和最大 SSL 版本服务器也返回一个 Hello 消息, 包含客户端需要的类似信息, 包括到底使用哪一个加密算法和 SSL 版本
证书交换 现在连接建立起来了, 服务器必须证明他的身份这个由 SSL 证书实现, 像护照一样 SSL 证书包含各种数据, 包含所有者名称, 相关属性(域名), 证书上的公钥, 数字签名和关于证书有效期的信息客户端检查它是不是被 CA 验证过的注意服务器被允许需求一个证书去证明客户端的身份, 但是这个只发生在敏感应用
密钥交换 先使用 RSA 非对称公钥加密算法 (客户端生成一个对称密钥, 然后用 SSL 证书里带的服务器公钥将改对称密钥加密随后发送到服务端, 服务端用服务器私钥解密, 到此, 握手阶段完成) 或者 DH 交换算法在客户端与服务端双方确定一将要使用的密钥, 这个密钥是双方都同意的一个简单, 对称的密钥, 这个过程是基于非对称加密方式和服务器的公钥 / 私钥的
加密通信 在服务器和客户端加密实际信息是用到对称加密算法, 用哪个算法在 Hello 阶段已经确定对称加密算法用对于加密和解密都很简单的密钥, 这个密钥是基于第三步在客户端与服务端已经商议好的与需要公钥 / 私钥的非对称加密算法相反
服务端的处理
静态缓存 CDN
为了优化网站访问速度并减少服务器压力, 通常将 htmljsCSS 文件这样的静态文件放在独立的缓存服务器或者部署在类似 Amazon CloudFront 的 CDN 云服务上, 然后根据缓存过期配置确定本次访问是否会请求源服务器来更新缓存
负载均衡
负载均衡具体实现有多种, 有直接基于硬件的 F5, 有操作系统传输层 (TCP) 上的 LVS, 也有在应用层 (HTTP) 实现的反向代理(也叫七层代理), 下面简单介绍一下最后者
在请求发送到真正处理请求的服务器之前, 还需要将请求路由到适合的服务器上, 一个请求被负载均衡器拿到之后, 需要做一些处理, 比如压缩请求 (在 nginx 中 gzip 压缩格式是默认配置在 nginx.conf 内的, 所以默认开启, 如果不对数据量要求特别精细的话, 默认配置完全可以满足基本需求) 接收请求(接收完毕后才发给 Server, 提高 Server 处理效率), 然后根据预定的路由算法, 将此次请求发送到某个后台服务器上
其中需要提到的一点是反向代理, 先理解一下正向代理的原理: 正向代理是将自己要访问的资源告诉 Proxy, 让 Proxy 帮你拿到数据返回给你, Proxy 服务于 Client, 常用于翻墙和跨权限操作反向代理也是将自己要访问的资源告诉 Proxy, 让 Proxy 帮你拿到数据返回给你, 但是 Proxy 会将请求接受完毕之后发送给某一合适的 Server, 这个时候 Client 是不知道是根据什么规则并且也不知道最后是哪一个 Server 服务于它的且 Proxy 服务于 Server, 所以叫反向代理, 常用于负载均衡安全控制
正向代理和反向代理
服务器的处理
对于 HTTPD(HTTP Daemon)在服务器上部署, 最常见的 HTTPD 有 Linux 上常用的 Apache 和 Nginx 对于 Java 平台来说, Tomcat 是 Spring Boot 也会默认选用的 Servlet 容器实现, 以 Tomcat 为例, 对于请求的处理如下:
请求到达 Tomcat 启动时监听的 TCP 端口
解析请求中的各种信息之后创建一个 Request 对象并填充那些有可能被所引用的 Servlet 使用的信息, 如参数, 头部 cookies 查询字符串等
创建一个 Response 对象, 所引用的 Servlet 使用它来给客户端发送响应
调用 Servlet 的 service 方法, 并传入 Request 和 Response 对象这里 Servlet 会从 Request 对象取值, 给 Response 写值
根据我们自己写的 Servlet 程序或者框架携带的 Servlet 类做进一步的处理(业务处理请求的进一步处理)
最后根据 Servlet 返回的 Response 生成相应的 HTTP 响应报文
浏览器的渲染
浏览器的功能是从服务器上取回你想要的资源, 然后展示在浏览器窗口当中资源通常是 HTML 文件, 也可能是 PDF, 图片, 或者其他类型的内容也可以显示其他类型的插件 (浏览器扩展) 例如显示 PDF 使用 PDF 浏览器插件资源的位置通过用户提供的 URI(Uniform Resource Identifier) 来确定
浏览器解释和展示 HTML 文件的方法, 在 HTML 和 CSS 的标准中有详细介绍这些标准由 web 标准组织 W3C(World Wide Web Consortium) 维护
图片来自: http://t.cn/hbvnTt
下面会以 Chrome 中使用的浏览器引擎 Webkit 为例, 根据上图来简单介绍浏览器的渲染具体解析渲染会涉及到非常多的细节, 请参考 HTML5 渲染规范和对应的页面 GPU 渲染实现
HTML 解析
浏览器拿到具体的 HTML 文档之后, 需要调用浏览器中使用的浏览器引擎中处理 HTML 的工具 (HTML Parser) 来将 HTML 文档解析成为 DOM 树, 将以便外部接口 (JS) 调用
文档内容解析: 将一大串字符串解析为 DOM 之前需要从中分析出结构化的信息让 HTML 解析器可以很方便地提取数据进行其他操作, 所以对于文档内容的解析是第一步解析器有两个处理过程词法分析 (将字符串切分成符合特定语法规范的符号) 与语法分析(根据符合语法规范的符号构建对应该文档的语法树)
HTML 解析: 根据 HTML 语法, 将 HTML 标记到语法树上构建成 DOM(Document Object Model)
CSS 解析
根据 CSS 词法和句法分析 CSS 文件和 < style > 标签包含的内容以及 style 属性的值
每个 CSS 文件都被解析成一个样式表对象(StyleSheet object), 这个对象里包含了带有选择器的 CSS 规则, 和对应 CSS 语法的对象
页面渲染
解析完成后, 浏览器引擎会通过 DOM 树和 CSS Rule 树来构造渲染树渲染树的构建会产生 Layout,Layout 是定位坐标和大小, 是否换行, 各种 position, overflow, z-index 属性的集合, 也就是对各个元素进行位置计算样式计算之后的结果
接下来, 根据渲染树对页面进行渲染(可以理解为画元素)
当然, 将这个渲染的过程完成并显示到屏幕上会涉及到显卡的绘制, 显存的修改, 有兴趣的读者可以深入了解本文只是将对于我们软件开发者来说需要了解的部分进行总结, 还有很多过程与机制没有提到, github 上的这个项目期待你的贡献
参考
- https://github.com/skyline75489/what-happens-when-zh_CN
- http://achuan.me/2017/03/01/20170301how-browser-works/
- https://zh.wikipedia.org/wiki/HTTP严格传输安全
- http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
- https://technet.microsoft.com/en-us/library/cc772774(v=ws.10).aspx.aspx)
- http://www.ruanyifeng.com/blog/2016/06/dns.html
- https://github.com/jawil/blog/issues/14
- http://robertheaton.com/2014/03/27/how-does-https-actually-work/
来源: http://insights.thoughtworks.cn/url-locates-the-world/