我们今天开始,来共同思考一下。一个浏览器到底是如何工作的。
实际上,对浏览器的实现者来说,他们做的事情,就是把一个 URL 变成一个屏幕上显示的网页。这个过程是这样的:
- 浏览器首先使用 HTTP 协议或者 HTTPS 协议,向服务端请求页面;
- 把请求回来的 HTML 代码经过解析,构建成 DOM 树;
- 计算 DOM 树上的 CSS 属性;
- 根据 CSS 属性对元素逐个进行渲染,得到内存中的位图;
- 一个可选的步骤是对位图进行合成,这会极大地增加后续绘制的速度;
- 合成之后,再绘制到界面上。
位图,又称为点阵图像、像素图或栅格图像,是由像素(图片元素)的单个点组成。这些点可以进行不同的排列和染色以构成图样。 位图的单位:像素(Pixel)
我们在开始详细介绍之前,要建立一个认知从 HTTP 请求回来开始,这个过程并非一般想象中的一步做完再做下一步,而是一条流水线。
从 HTTP 请求回来,就产生了流式的数据,后续的 DOM 树构建、CSS 计算、渲染、合成、绘制,都是尽可能地流式处理前一步的产出:即不需要等到上一步骤完全结束,就开始处理上一步的输出,这样我们在浏览网页时,才会看到逐步出现的页面。
首先我从咱们最常用的HTTP协议开始讲起。
HTTP协议,几乎是每个人上网用的第一个协议,同时也是很容易被人忽略的协议。
比如我们要查资料,就先登录 http://www.baidu.com , http://www.baidu.com 是个URL,叫作统一资源定位符。之所以叫统一,是因为它是有格式的。HTTP称为协议, http://www.baidu.com 是一个域名,表示互联网上的一个位置。有的URL会有更详细的位置标识,例如 http://www.baidu.com/index.html。正是因为这个东西是统一的,所以当你把这样一个字符串输入到浏览器的框里的时候,浏览器才知道如何进行统一处理。
HTTP 请求的准备浏览器会将 http://www.baidu.com 这个域名发送给DNS服务器,让它解析为IP地址。有关DNS的过程,其实非常复杂,这个在后面专门介绍DNS的时候,我会详细描述,这里我们先不管,反正它会被解析成IP地址,那么接下来是发送HTTP请求吗?
不是的,HTTP请求是基于TCP协议的,当然要先建立TCP连接了,怎么建立这个稍后会讲到。
HTTP请求构建
建立了连接以后,浏览器就要发送HTTP请求。
我先来了解下 HTTP 的标准。HTTP 标准由 IETF 组织制定,跟它相关的标准主要有两份:
HTTP 协议是基于 TCP 协议出现的,对 TCP 协议来说,TCP 协议是一条双向的通讯通道,HTTP 在 TCP 的基础上,规定了 Request-Response 的模式。这个模式决定了通讯必定是由浏览器端首先发起的。
大部分情况下,浏览器的实现者只需要用一个 TCP 库,甚至一个现成的 HTTP 库就可以搞定浏览器的网络通讯部分。HTTP 是纯粹的文本协议,它是规定了使用 TCP 协议来传输文本格式的一个应用层协议。
实验
我们的实验需要使用 telnet 客户端,这个客户端是一个纯粹的 TCP 连接工具(安装方法)。首先我们运行 telnet,连接到极客时间主机,在命令行里输入以下内容:
telnet time.geekbang.org 80
这个时候,TCP 连接已经建立,我们输入以下字符作为请求:
GET / HTTP/1.1
Host: time.geekbang.org
按下两次回车,我们收到了服务端的回复:
HTTP/1.1 301 Moved Permanently
Date: Fri, 25 Jan 2019 13:28:12 GMT
Content-Type: text/html
Content-Length: 182
Connection: keep-alive
Location: https://time.geekbang.org/
Strict-Transport-Security: max-age=15768000
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty</center>
</body>
</html>
在请求部分,第一行被称作 request line,它分为三个部分,HTTP Method,也就是请求的“方法”,请求的路径和请求的协议和版本。
在响应部分,第一行被称作 response line,它也分为三个部分,协议和版本、状态码和状态文本。
HTTP 协议格式
根据上面的分析,我们可以知道 HTTP 协议,大概可以划分成如下部分。
我们简单看一下,在这些部分中,path 是请求的路径完全由服务端来定义,没有很多的特别内容;而 version 几乎都是固定字符串;response body 是我们最熟悉的 HTML。
HTTP Method(方法)
我们首先来介绍一下 request line 里面的方法部分。这里的方法跟我们编程中的方法意义类似,表示我们此次 HTTP 请求希望执行的操作类型。方法有以下几种定义:
- GET
- POST
- HEAD
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
浏览器通过地址栏访问页面都是 GET 方法。表单提交产生 POST 方法。
HEAD 则是跟 GET 类似,只返回响应头,多数由 JavaScript 发起。
PUT 和 DELETE 分别表示添加资源和删除资源,但是实际上这只是语义上的一种约定,并没有强约束。
CONNECT 现在多用于 HTTPS 和 WebSocket。
OPTIONS 和 TRACE 一般用于调试,多数线上服务都不支持。
HTTP Status code(状态码)和 Status text(状态文本)
接下来我们看看 response line 的状态码和状态文本。常见的状态码有以下几种。
- 1xx:临时回应,表示客户端请继续。
2xx:请求成功。
200:请求成功。
3xx: 表示请求的目标有变化,希望客户端进一步处理。
301&302:永久性与临时性跳转。<br /> 304:跟客户端缓存没有更新。
4xx:客户端请求错误。
403:无权限。<br /> 404:表示请求的页面不存在。
5xx:服务端请求错误。
500:服务端错误。<br /> 503:服务端暂时性错误,可以一会再试。
对我们前端来说,1xx 系列的状态码是非常陌生的,原因是 1xx 的状态被浏览器 HTTP 库直接处理掉了,不会让上层应用知晓。
2xx 系列的状态最熟悉的就是 200,这通常是网页请求成功的标志,也是大家最喜欢的状态码。
3xx 系列比较复杂,301 和 302 两个状态表示当前资源已经被转移,只不过一个是永久性转移,一个是临时性转移。实际上 301 更接近于一种报错,提示客户端下次别来了。
304 又是一个每个前端必知必会的状态,产生这个状态的前提是:客户端本地已经有缓存的版本,并且在 Request 中告诉了服务端,当服务端通过时间或者 tag,发现没有更新的时候,就会返回一个不含 body 的 304 状态。
HTTP Head (HTTP 头)
HTTP 头可以看作一个键值对。原则上,HTTP 头也是一种数据,我们可以自由定义 HTTP 头和值。不过在 HTTP 规范中,规定了一些特殊的 HTTP 头,我们现在就来了解一下它们。
在 HTTP 标准中,有完整的请求 / 响应头规定,这里我们挑几个重点的说一下:我们先来看看 Request Header。
接下来看一下 Response Header。
这里仅仅列出了我认为比较常见的 HTTP 头,这些头是我认为前端工程师应该做到不需要查阅,看到就可以知道意思的 HTTP 头。
HTTP Request Body
HTTP 请求的 body 主要用于提交表单场景。实际上,HTTP 请求的 body 是比较自由的,只要浏览器端发送的 body 服务端认可就可以了。一些常见的 body 格式是:
- application/json
- application/x-www-form-urlencoded
- multipart/form-data
- text/xml
我们使用 HTML 的 form 标签提交产生的 HTML 请求,默认会产生 application/x-www-form-urlencoded 的数据格式,当有文件上传时,则会使用 multipart/form-data。
HTTPS
在 HTTP 协议的基础上,HTTPS 和 HTTP2 规定了更复杂的内容,但是它基本保持了 HTTP 的设计思想,即:使用上的 Request-Response 模式。
我们首先来了解下 HTTPS。HTTPS 有两个作用,一是确定请求的目标服务端身份,二是保证传输的数据不会被网络中间节点窃听或者篡改。
HTTPS 的标准也是由 RFC 规定的,你可以查看它的详情链接:
https://tools.ietf.org/html/rfc2818
HTTPS 是使用加密通道来传输 HTTP 的内容。但是 HTTPS 首先与服务端建立一条 TLS 加密通道。TLS 构建于 TCP 协议之上,它实际上是对传输的内容做一次加密,所以从传输内容上看,HTTPS 跟 HTTP 没有任何区别。
HTTP 2
HTTP 2 是 HTTP 1.1 的升级版本,你可以查看它的详情链接。
https://tools.ietf.org/html/rfc7540
HTTP 2.0 最大的改进有三点,一是支持服务端推送,二是支持 TCP 连接复用, 三是支持二进制代理文本进行传输,极大提高了传输的效率。
小段总结下我们一起学习了浏览器的第一步工作,也就是“浏览器首先使用 HTTP 协议或 HTTPS 协议,向服务端请求页面”的这一过程。但是有块内容我们暂时还没有讲就是TCP连接。
我们还是从访问一个电商网站说起, 当我们在浏览器里面输入 https://www.kaola.com ,这是一个 URL。浏览器只知道名字是“www.kaola.com”,但是不知道具体的地点,所以不知道应该如何访问。于是,它打开地址簿去查找。可以使用一般的地址簿协议 DNS 去查找,还可以使用另一种更加精准的地址簿查找协议 HTTPDNS。
无论用哪一种方法查找,最终都会得到这个地址:106.114.138.24。这个是 IP 地址,是互联网世界的“门牌号”。
知道了目标地址,浏览器就开始打包它的请求。对于普通的浏览请求,往往会使用 HTTP 协议;但是对于购物的请求,往往需要进行加密传输,因而会使用 HTTPS 协议。无论是什么协议,里面都会写明“你要买什么和买多少”。
DNS、HTTP、HTTPS 所在的层我们称为应用层。
OSI模型概述
OSI模型是一种网络模型,简单讲就是两台计算机要进行网络通讯的话从物理层、数据链路层和网络层从通讯模型上统一标准。这个模型是由国际标准化组织(ISO)于1974年首次提出的。它由七层组成—物理层、数据链路层、网络层、传输层、会话层、显示层和应用层。每个层都有自己的任务,这些任务是独立完成的。
现在我们明白了吧? 你发送的HTTP请求在网络通讯中定义是在应用层,经过应用层封装后,浏览器会将应用层的包交给下一层传输层。传输层有两种协议,一种是无连接的协议 UDP,一种是面向连接的协议 TCP。对于支付来讲,往往使用 TCP 协议。所谓的面向连接就是,TCP 会保证这个包能够到达目的地。如果不能到达,就会重新发送,直至到达。
TCP 协议里面会有两个端口,一个是浏览器监听的端口,一个是电商的服务器监听的端口。操作系统往往通过端口来判断,它得到的包应该给哪个进程。
传输层封装完毕后,浏览器会将包交给操作系统的网络层。网络层的协议是 IP 协议。在 IP 协议里面会有源 IP 地址,即浏览器所在机器的 IP 地址和目标 IP 地址,也即电商网站所在服务器的 IP 地址。
操作系统既然知道了目标 IP 地址,就开始想如何根据这个门牌号找到目标机器。操作系统往往会判断,这个目标 IP 地址是本地人,还是外地人。如果是本地人,从门牌号就能看出来,但是显然电商网站不在本地,而在遥远的地方。
操作系统知道要离开本地去远方。虽然不知道远方在何处,但是可以这样类比一下:如果去国外要去海关,去外地就要去网关。而操作系统启动的时候,就会被 DHCP 协议配置 IP 地址,以及默认的网关的 IP 地址 192.168.1.1。
操作系统如何将 IP 地址发给网关呢?在本地通信基本靠吼,于是操作系统大吼一声,谁是 192.168.1.1 啊?网关会回答它,我就是,我的本地地址在村东头。这个本地地址就是 MAC 地址,而大吼的那一声是 ARP 协议。
什么是MAC地址 MAC地址就是在媒体接入层上使用的地址,也叫物理地址或硬件地址,由网络设备制造商生产时写在硬件内部。MAC地址与网络无关,它由厂商固化在网卡的BIOS里。主要用于确认网上设备的地址,类似于身份证号,具有唯一标识。 查看本机的 MAC 地址 cmd 输入 ipconfig -all
于是操作系统将 IP 包交给了下一层,也就是 MAC 层。网关收到包之后,会根据自己的知识,判断下一步应该怎么走。网关往往是一个路由器,到某个 IP 地址应该怎么走,这个叫作路由表。
路由器有点像玄奘西行路过的一个个国家的一个个城关。每个城关都连着两个国家,每个国家相当于一个局域网,在每个国家内部,都可以使用本地的地址 MAC 进行通信。
一旦跨越城关,就需要拿出 IP 头来,里面写着源 IP 地址,和目标 IP 地址。城关(路由器)往往是知道这些“知识”的,因为路由器和临近的路由器也会经常沟通。到哪里应该怎么走,这种沟通的协议称为路由协议,常用的有 OSPF 和 BGP。
当网络包通过路由器的指引找到了目的地(目标IP地址),目标服务器会通过一系列的判断(分析这个网络的MAC地址 请求的目标IP地址)如果指向的不是自己那就应该转发出去;如果是自己的,那就是发给自己的。根据 IP 头里面的标示,拆解网络包中的信息且发送一个回复包,沿着刚才来的方向走回去,当网络包平安到达 TCP 层之后,TCP 头中有目标端口号,通过这个端口号,可以找到电商网站的进程正在监听这个端口号,将这个包发给电商网站。
电商网站的进程得到 HTTP 请求的内容,知道了要买东西,买多少。电商网站控制平台统筹处理这个请求,告诉专门管理订单的进程,登记要买某个商品,买多少,要告诉管理库存的进程,库存要减少多少,要告诉支付的进程,应该付多少钱,等等。
如何告诉相关的进程呢?往往通过 RPC 调用,即远程过程调用的方式来实现。远程过程调用就是当告诉管理订单进程的时候,接待员不用关心中间的网络互连问题,会由 RPC 框架统一处理。RPC 框架有很多种,有基于 HTTP 协议放在 HTTP 的报文里面的,有直接封装在 TCP 报文里面的。
控制平台发现相应的模块都处理完毕,就回复一个 HTTPS 的包,告知下单成功。这个 HTTPS 的包,会像来的时候一样,经过千难万险到达你的个人电脑,最终进入浏览器,显示支付成功。