一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第29天,点击查看活动详情。
HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,它建立在 TCP 连接基础之上。通常由浏览器发起请求,用来获取不同类型的文件,例如 HTML 文件、CSS 文件、JavaScript 文件、图片、视频等。
本文将分析一个完整的 HTTP 请求过程是怎么样的。
构建请求
首先,浏览器构建请求行信息,构建好后,浏览器准备发起网络请求。
GET /index.html HTTP1.1
查找缓存
在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件。其中,浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。
准备 IP 地址和端口
因为浏览器使用 HTTP 协议作为应用层协议,用来封装请求的文本信息;并使用 TCP/IP 作传输层协议将它发到网络上,所以在 HTTP 工作开始之前,浏览器需要通过 TCP 与服务器建立连接。也就是说 HTTP 的内容是通过 TCP 的传输数据阶段来实现的。
HTTP 网络请求的第一步是和服务器建立 TCP 连接,那建立连接需要准备 IP 地址和端口号。
由于 IP 地址是数字标识,比如39.106.233.176, 难以记忆,但使用域名(baidu.com)就好记多了,所以基于这个需求又出现了一个服务,负责把域名和 IP 地址做一一映射关系。这套域名映射为 IP 的系统就叫做"域名系统",简称 DNS(Domain Name System)
。
所以,第一步浏览器会请求 DNS 返回域名对应的 IP。当然浏览器还提供了 DNS 数据缓存服务,如果某个域名已经解析过了,那么浏览器会缓存解析的结果,以供下次查询时直接使用,这样也会减少一次网络请求。
拿到 IP 之后,接下来就需要获取端口号了。通常情况下,如果 URL 没有特别指明端口号,那么 HTTP 协议默认是 80 端口。
等待 TCP 队列
现在已经把端口和 IP 地址都准备好了,那么下一步是不是可以建立 TCP 连接了呢?
答案依然是"不行"。Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。
排队等待结束之后,终于可以快乐地和服务器握手了,一旦建立了 TCP 连接,浏览器就可以和服务器进行通信了。而 HTTP 中的数据正是在这个通信过程中传输的。
服务器端处理 HTTP 请求流程
返回请求
一旦服务器处理结束,便可以返回数据给浏览器了。可以通过工具软件 curl 来查看返回请求数据,具体使用方法是在命令行中输入以下命令:
curl -i https://time.geekbang.org/
注意:这里加上了-i是为了返回响应行、响应头和响应体的数据
返回的结果如下图所示,你可以结合这些数据来理解服务器是如何响应浏览器的。
首先服务器会返回响应行,包括协议版本和状态码。但并不是所有的请求都可以被服务器处理的,那么一些无法处理或者处理出错的信息,怎么办呢?
服务器会通过状态码来告诉浏览器它的处理结果,比如:
- 最常用的状态码是 200,表示处理成功;
- 如果没有找到页面,则会返回 404。
服务器也会随同响应行向浏览器发送响应头。响应头包含了服务器自身的一些信息,比如服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客户端保存的 Cookie 等信息。
发送完响应头后,服务器就可以继续发送响应体的数据,通常,响应体就包含了 HTML 的实际内容。
断开连接
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了:
Connection:Keep-Alive
那么 TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接发送请求。保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。
重定向
比如当你在浏览器中打开 geekbang.org 后,你会发现最终打开的页面地址是 www.geekbang.org。 这两个 URL 之所以不一样,是因为涉及到了一个重定向操作。
使用 curl 来查看下请求 geekbang.org 会返回什么内容。
curl -I geekbang.org
I表示只需要获取响应头和响应行数据,而不需要获取响应体的数据
响应行返回的状态码是 301,状态 301 就是告诉浏览器,我需要重定向到另外一个网址,而需要重定向的网址正是包含在响应头的 Location
字段中,接下来,浏览器获取 Location
字段中的地址,并使用该地址重新导航,这就是一个完整重定向的执行流程。
不过也不要认为这种跳转是必然的。如果打开 12306.cn, 会发现这个站点是打不开的。这是因为 12306 的服务器并没有处理跳转,所以必须要手动输入完整的 www.12306.cn 才能打开页面。
为什么很多站点第二次打开速度会很快?
如果第二次页面打开很快,主要原因是第一次加载页面过程中,缓存了一些耗时的数据。
那么,哪些数据会被缓存呢?DNS 缓存和页面资源缓存这两块数据是会被浏览器缓存的。其中,DNS 缓存比较简单,它主要就是在浏览器本地把对应的 IP 和域名关联起来。
重点看下浏览器资源缓存,下面是缓存处理的过程:
当服务器返回 HTTP 响应头给浏览器时,浏览器是通过响应头中的 Cache-Control
字段来设置是否缓存该资源。通常,我们还需要为这个资源设置一个缓存过期时长,而这个时长是通过 Cache-Control
中的 Max-age
参数来设置的,比如上图设置的缓存过期时间是 2000 秒。
Cache-Control:Max-age=2000
这也就意味着,在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。
但如果缓存过期了,浏览器则会继续发起网络请求,并且在 HTTP 请求头中带上:
If-None-Match:"4f80f-13c-3a1xb12a"
服务器收到请求头后,会根据 If-None-Match 的值来判断请求的资源是否有更新。
- 如果没有更新,就返回
304
状态码,相当于服务器告诉浏览器:"这个缓存可以继续使用,这次就不重复发送数据给你了。" - 如果资源有更新,服务器就直接返回最新资源给浏览器。
简要来说,很多网站第二次访问能够秒开,是因为这些网站把很多资源都缓存在了本地,浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,从而节省了时间。同时,DNS 数据也被浏览器缓存了,这又省去了 DNS 查询环节。
登录状态是如何保持的?
-
首次登录时,服务器接收到浏览器提交的信息之后,查询后台,验证用户登录信息是否正确,如果正确的话,会生成一段表示用户身份的字符串,并把该字符串写到响应头的
Set-Cookie
字段里,如下所示,然后把响应头发送给浏览器。Set-Cookie: UID=3431uad;
-
浏览器在接收到服务器的响应头后,开始解析响应头,如果遇到响应头里含有 Set-Cookie 字段的情况,浏览器就会把这个字段信息保存到本地。比如把UID=3431uad保持到本地。
-
当用户再次访问时,浏览器会发起 HTTP 请求,但在发起请求之前,浏览器会读取之前保存的 Cookie 数据,并把数据写进请求头里的 Cookie 字段里,然后浏览器再将请求头发送给服务器。
Cookie: UID=3431uad;
-
服务器在收到 HTTP 请求头数据之后,就会查找请求头里面的
Cookie
字段信息,当查找到包含UID=3431uad的信息时,服务器查询后台,并判断该用户是已登录状态,然后生成含有该用户信息的页面数据,并把生成的数据发送给浏览器。
下面用一张图来展现浏览器中的 HTTP 请求所经历的各个阶段: