我们今天开始,来共同思考一下。一个浏览器到底是如何工作的。
实际上,对浏览器的实现者来说,他们做的事情,就是把一个 URL 变成一个屏幕上显示的网页。这个过程是这样的:

  • 浏览器首先使用 HTTP 协议或者 HTTPS 协议,向服务端请求页面;
  • 把请求回来的 HTML 代码经过解析,构建成 DOM 树;
  • 计算 DOM 树上的 CSS 属性;
  • 根据 CSS 属性对元素逐个进行渲染,得到内存中的位图;
  • 一个可选的步骤是对位图进行合成,这会极大地增加后续绘制的速度;
  • 合成之后,再绘制到界面上。

位图,又称为点阵图像、像素图或栅格图像,是由像素(图片元素)的单个点组成。这些点可以进行不同的排列和染色以构成图样。 位图的单位:像素(Pixel)

image.png

我们在开始详细介绍之前,要建立一个认知从 HTTP 请求回来开始,这个过程并非一般想象中的一步做完再做下一步,而是一条流水线。

从 HTTP 请求回来,就产生了流式的数据,后续的 DOM 树构建、CSS 计算、渲染、合成、绘制,都是尽可能地流式处理前一步的产出:即不需要等到上一步骤完全结束,就开始处理上一步的输出,这样我们在浏览网页时,才会看到逐步出现的页面。

首先我从咱们最常用的HTTP协议开始讲起。

HTTP协议,几乎是每个人上网用的第一个协议,同时也是很容易被人忽略的协议。

比如我们要查资料,就先登录 http://www.baidu.comhttp://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,连接到极客时间主机,在命令行里输入以下内容:

  1. telnet time.geekbang.org 80

这个时候,TCP 连接已经建立,我们输入以下字符作为请求:

  1. GET / HTTP/1.1
  2. Host: time.geekbang.org

按下两次回车,我们收到了服务端的回复:

  1. HTTP/1.1 301 Moved Permanently
  2. Date: Fri, 25 Jan 2019 13:28:12 GMT
  3. Content-Type: text/html
  4. Content-Length: 182
  5. Connection: keep-alive
  6. Location: https://time.geekbang.org/
  7. Strict-Transport-Security: max-age=15768000
  8. <html>
  9. <head><title>301 Moved Permanently</title></head>
  10. <body bgcolor="white">
  11. <center><h1>301 Moved Permanently</h1></center>
  12. <hr><center>openresty</center>
  13. </body>
  14. </html>

在请求部分,第一行被称作 request line,它分为三个部分,HTTP Method,也就是请求的“方法”,请求的路径和请求的协议和版本。

在响应部分,第一行被称作 response line,它也分为三个部分,协议和版本、状态码和状态文本。

HTTP 协议格式

根据上面的分析,我们可以知道 HTTP 协议,大概可以划分成如下部分。
image.png
我们简单看一下,在这些部分中,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:请求成功。

    1. 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。
HTTP请求流程 - 图3
接下来看一下 Response Header。
HTTP请求流程 - 图4
这里仅仅列出了我认为比较常见的 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 协议。无论是什么协议,里面都会写明“你要买什么和买多少”。
image.png
DNS、HTTP、HTTPS 所在的层我们称为应用层。

怎么理解”层”概念,他的来源是OSI模型。

OSI模型概述

OSI模型是一种网络模型,简单讲就是两台计算机要进行网络通讯的话从物理层、数据链路层和网络层从通讯模型上统一标准。这个模型是由国际标准化组织(ISO)于1974年首次提出的。它由七层组成—物理层、数据链路层、网络层、传输层、会话层、显示层和应用层。每个层都有自己的任务,这些任务是独立完成的。
image.png
现在我们明白了吧? 你发送的HTTP请求在网络通讯中定义是在应用层,经过应用层封装后,浏览器会将应用层的包交给下一层传输层。传输层有两种协议,一种是无连接的协议 UDP,一种是面向连接的协议 TCP。对于支付来讲,往往使用 TCP 协议。所谓的面向连接就是,TCP 会保证这个包能够到达目的地。如果不能到达,就会重新发送,直至到达。

TCP 协议里面会有两个端口,一个是浏览器监听的端口,一个是电商的服务器监听的端口。操作系统往往通过端口来判断,它得到的包应该给哪个进程。
image.png

传输层封装完毕后,浏览器会将包交给操作系统的网络层。网络层的协议是 IP 协议。在 IP 协议里面会有源 IP 地址,即浏览器所在机器的 IP 地址和目标 IP 地址,也即电商网站所在服务器的 IP 地址。
image.png
操作系统既然知道了目标 IP 地址,就开始想如何根据这个门牌号找到目标机器。操作系统往往会判断,这个目标 IP 地址是本地人,还是外地人。如果是本地人,从门牌号就能看出来,但是显然电商网站不在本地,而在遥远的地方。

操作系统知道要离开本地去远方。虽然不知道远方在何处,但是可以这样类比一下:如果去国外要去海关,去外地就要去网关。而操作系统启动的时候,就会被 DHCP 协议配置 IP 地址,以及默认的网关的 IP 地址 192.168.1.1。

操作系统如何将 IP 地址发给网关呢?在本地通信基本靠吼,于是操作系统大吼一声,谁是 192.168.1.1 啊?网关会回答它,我就是,我的本地地址在村东头。这个本地地址就是 MAC 地址,而大吼的那一声是 ARP 协议。

什么是MAC地址 MAC地址就是在媒体接入层上使用的地址,也叫物理地址或硬件地址,由网络设备制造商生产时写在硬件内部。MAC地址与网络无关,它由厂商固化在网卡的BIOS里。主要用于确认网上设备的地址,类似于身份证号,具有唯一标识。 查看本机的 MAC 地址 cmd 输入 ipconfig -all

image.png
于是操作系统将 IP 包交给了下一层,也就是 MAC 层。网关收到包之后,会根据自己的知识,判断下一步应该怎么走。网关往往是一个路由器,到某个 IP 地址应该怎么走,这个叫作路由表。

路由器有点像玄奘西行路过的一个个国家的一个个城关。每个城关都连着两个国家,每个国家相当于一个局域网,在每个国家内部,都可以使用本地的地址 MAC 进行通信。

一旦跨越城关,就需要拿出 IP 头来,里面写着源 IP 地址,和目标 IP 地址。城关(路由器)往往是知道这些“知识”的,因为路由器和临近的路由器也会经常沟通。到哪里应该怎么走,这种沟通的协议称为路由协议,常用的有 OSPF 和 BGP。

当网络包通过路由器的指引找到了目的地(目标IP地址),目标服务器会通过一系列的判断(分析这个网络的MAC地址 请求的目标IP地址)如果指向的不是自己那就应该转发出去;如果是自己的,那就是发给自己的。根据 IP 头里面的标示,拆解网络包中的信息且发送一个回复包,沿着刚才来的方向走回去,当网络包平安到达 TCP 层之后,TCP 头中有目标端口号,通过这个端口号,可以找到电商网站的进程正在监听这个端口号,将这个包发给电商网站。

电商网站的进程得到 HTTP 请求的内容,知道了要买东西,买多少。电商网站控制平台统筹处理这个请求,告诉专门管理订单的进程,登记要买某个商品,买多少,要告诉管理库存的进程,库存要减少多少,要告诉支付的进程,应该付多少钱,等等。

如何告诉相关的进程呢?往往通过 RPC 调用,即远程过程调用的方式来实现。远程过程调用就是当告诉管理订单进程的时候,接待员不用关心中间的网络互连问题,会由 RPC 框架统一处理。RPC 框架有很多种,有基于 HTTP 协议放在 HTTP 的报文里面的,有直接封装在 TCP 报文里面的。

控制平台发现相应的模块都处理完毕,就回复一个 HTTPS 的包,告知下单成功。这个 HTTPS 的包,会像来的时候一样,经过千难万险到达你的个人电脑,最终进入浏览器,显示支付成功。