HTTP(Hypertext Transport Protocol)超文本传输协议。在浏览器领域,HTTP是一种允许浏览器向服务器获取资源的协议,是Web的基础,通常由浏览器发起请求,用来获取不同类型的文件, 例如HTML文件、CSS文件、Javascript文件、图片、视频等。
引:
1️⃣ 为什么通常在第一次访问一个站点时,打开速度很慢,当再次访问这个站点时,速度就很快了?
2️⃣ 当登录过一个网站之后,下次再访问该站点,就已经处于登录状态了, 这是怎么做到的?
答案都与HTTP的请求过程息息相关。
浏览器端发起HTTP请求流程
如果在浏览器地址栏中输入一个网站的地址: 如time.geekbang.org/index.html ,那么浏览器会完成哪些动作呢?
1. 构建请求
首先, 浏览器构建请求行信息,构建好后。浏览器准备发起网络请求。
GET /index.html HTTP1.1
2.查找缓存
在真正发起网络请求之前, 浏览器会先在浏览器缓存中查询是否有要请求的文件。其中,浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。
当浏览器发现请求的资源已经在浏览器缓存中存有副本, 它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。这样做的好处有:
- 缓解服务器端的压力,提升性能(节省获取资源的耗时);
- 对于网站来讲,缓存是实现快速资源加载的重要组成部分。
如果缓存查找并没有命中,则进入下一步。
3.准备IP地址和端口
浏览器使用HTTP协议作为应用层协议,用来封装请求的文本信息;并使用TCP/IP作为传输层协议将它发到网络上,所以在HTTP工作开始之前,浏览器需要通过TCP与服务器建立连接,也就是说HTTP的内容是通过TCP的传输数据阶段来实现的。
简略地看, HTTP网络请求的第一步,就是和服务器建立TCP连接。 而建立TCP连接的第一步就是准备IP地址和端口号。
那么如何获取IP地址和端口号呢?
截止目前我们已有的信息就是地址栏中键入的URL。数据包都是通过IP地址传输给接收方的。由于IP地址是数字标识(如39.106.233.176)难以记忆,而使用域名(time.geekbang.org)就好记多了,所以基于这个需求衍生了一个服务,负责把域名和IP地址做一一映射关系,这套域名映射为IP的系统就是“域名系统”,简称DNS(Domain Name System)。
所以,发起真正的请求的第一步,浏览器会请求DNS返回域名对应的IP。(如果某个域名已经解析过了,那么浏览器会缓存解析的结果,以供下次查询时直接使用,这样也会减少一次网络请求)
拿到IP之后,接下来就是获取端口号,通常情况下,如果URL没有特别指明端口号,那么HTTP协议默认是80端口,HTTPS默认是443端口。
4.等待TCP队列
准备好IP和端口号后,就可以建立TCP连接了吗?
答案是否定的,如在Chrome浏览器下,同一个域名同时最多只能建立6个TCP连接,如果在同一域名下同时有10个请求发生,那么其中4个请求会进入排队等待状态,直到进行中的请求完成。
如果当前请求数量少于6,会直接进入下一步,建立TCP连接。
HTTP/1.1中, 一个TCP同时只能处理一个请求,浏览器会为每个域名维护6个TCP连接!但是每个TCP连接是可以复用的,也就是会处理完一个请求之后,不断开这个TCP连接,可以用来处理下一个HTTP请求;
HTTP/2中是可以并行请求资源的,所以如果使用HTTP2,浏览器只会为每个域名维护一个TCP连接。
5. 建立TCP连接
在HTTP工作开始之前,浏览器通过TCP与服务器建立连接(这一流程内容详见# 学习浏览器 - 网络协议)
6.发送HTTP请求
一旦建立了TCP连接,浏览器就可以和服务器进行通信了。而HTTP中的数据正是在这个通信过程中传输的。
首先浏览器会向服务器发送请求行,它包括了请求方法、请求URI(Uniform Resource Identifier)和HTTP协议版本。
发送请求行,就是告诉服务器浏览器需要什么资源,最常用的请求方法是 Get。比如,直接在浏览器地址栏键入(www.baidu.com) 就是告诉服务器要 Get 它的首页资源。
另外一个常用的请求方法是 POST, 它用于发送一些数据给服务器,比如登录一个网站,就需要通过POST方法把用户信息发送给服务器。如果使用POST方法,那么浏览器还要准备数据给服务器,这里准备的数据是通过请求体发送。
在浏览器发送请求行命令后, 还要以请求头形式发送一些其他信息,把浏览器的一些基础信息告诉服务器。(如浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的Cookie信息等等)
服务器端处理 HTTP 请求流程
经过上面的流程,HTTP的请求被送达到了服务器。接下来, 服务器会根据浏览器的请求信息来准备相应的内容。
1.返回请求
一旦服务器处理结束,便可以返回数据给浏览器了,可以通过工具软件 curl 来查看返回请求数据,在终端输入以下命令:
//-i返回响应行、响应头和响应体的数据
curl -i https://time.geekbang.org/
首先服务器会返回响应行,包括协议版本和状态码。
随后,正如浏览器会随同请求发送请求头一样, 服务器也会随同响应向浏览器发送响应头。响应头包含了服务器自身的一些信息, 比如服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型)。以及服务器要在客户端保存的Cookie等信息。
发送完响应头后,服务器就可以继续发送响应体的数据,通常,响应体就包含了HTML的实际内容。
2. 断开连接
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭TCP连接。不过如果浏览器或者服务器在其头信息中加入了
Connection:Keep-Alive
那么TCP连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个TCP连接发送请求。保持TCP连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。比如,一个Web页面中内嵌的图片就都来自同一个Web站点,如果初始化了一个持久连接,就可以复用该连接,以请求其他资源,而不需要重新再建立新的TCP连接。
3.重定向
当你在浏览器中打开geekbang.org后,你会发现最终打开的页面地址是 www.geekbang.org 。这两个URL之所以不一样,是因为涉及到了一个重定向操作。可以使用curl来查看请求geekbang.org会返回什么内容?
// 不同于-i, -I表示只需要获取响应头和响应行数据,而不需要获取响应体的数据
curl -I geekbang.org
响应行返回的状态码是301, 状态301就是告诉浏览器,需要重定向到另外一个网址,而需要重定的网址就包含在响应头的Location字段中。接下来,浏览器获取Location字段中的地址,并使用该地址重新导航,这就是一个完整重定向的执行流程。
附总结图:
浏览器中的HTTP请求从发起到结束一共经历了如下八个阶段:
- 构建请求
- 查找缓存
- 准备IP和端口
- 等待TCP队列
- 建立TCP连接
- 发起HTTP请求
- 服务器处理请求
- 服务器返回请求和断开连接。
解:
回到文章开头”引“部分的问题。
1️⃣ 为什么通常在第一次访问一个站点时,打开速度很慢,当再次访问这个站点时,速度就很快了?
如果第二次页面打开很快,主要原因是第一次加载页面过程中,缓存了一些耗时的数据。
那么, 哪些数据会被缓存呢?从上面介绍的核心请求路径可以发现,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查询环节。
2️⃣ 当登录过一个网站之后,下次再访问该站点,就已经处于登录状态了, 这是怎么做到的?
- 用户打开登录页面,在登录框里填入用户名和密码,点击确定按钮。触发页面脚本生成用户登录信息,然后调用POST方法提交用户登录信息给服务器;
- 服务器接收到浏览器提交的信息之后,查询后台,验证用户登录信息是否正确,如果正确,会生成一段表示用户身份的字符串,并把该字符串写到响应头的Set-Cookie字段中,然后把响应头发送给浏览器。
Set-Cookie: UID=3431uad;
- 浏览器在接收到服务器的响应头后,解析响应头,遇到响应头里含有Set-Cookie字段,浏览器就会把这个字段信息保存到本地;
- 当用户再次访问时,浏览器会发起HTTP请求,但在发起请求之前,浏览器会读取之前保存的Cookie数据,并把数据写进请求头中的Cookie字段中,然后浏览器再将请求头发送给服务器.
Cookie: UID=3431uad;
- 服务器在收到HTTP请求头数据之后,就会查找请求头里的Cookie字段信息,当查到包含有UID=3431uad的信息时,服务器查询后台,并判断该用户是以登录状态,然后生成含有该用户信息的页面数据,并把生成的数据发送给浏览器。
- 浏览器在接收到该含有当前用户的页面数据后,就可以正确展示用户登录的状态信息了。
也就是说,登录状态是通过Cookie实现的。