2021前端面试题系列:HTTP请求和HTTP缓存控制

677 阅读16分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

大家好,我是前端岚枫,一枚二线城市的程序媛,今天主要跟大家分享我整理的笔记2021前端面试题系列:HTTP请求和HTTP缓存控制,此方面内容在我们的工作中常用到, 也是面试官经常提问的问题,希望下面文章对大家有所帮助。

1. 一次完整的HTTP服务过程

问题分析

当我们在web浏览器的地址栏中输入:www.xxxx.com,具体发生了什么?

  1. www.xxxx.com这个网址进行DNS域名解析,得到对应的IP地址
  2. 根据这个IP,找到对应的服务器,发起TCP的三次握手
  3. 建立TCP连接后发起HTTP请求
  4. 服务器响应HTTP请求,浏览器得到html代码
  5. 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)(先得到html代码,才能去找这些资源)
  6. 浏览器对页面进行渲染呈现给用户
  7. 服务器关闭关闭TCP连接

注:

  1. DNS怎么找到域名的?

DNS域名解析采用的是递归查询的方式,过程是,先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器

  1. 为什么HTTP协议要基于TCP来实现?

TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,会重传)

  1. 最后一步浏览器是如何对页面进行渲染的?

a)解析html文件构成 DOM树 b)解析CSS文件构成渲染树 c)边解析,边渲染 d)JS 单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载

各个步骤具体细节

DNS解析(域名解析服务器)image.png

  1. 首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)
  2. 如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的DNS缓存
  3. 如果还没有找到,那么尝试从 hosts文件里面去找
  4. 在前面三个过程都没获取到的情况下,就递归地去域名服务器去查找,具体过程如下

5rxqugj8dh.png

DNS优化两个方面:DNS缓存、DNS负载均衡

TCP连接建立(三次握手)

拿到域名对应的IP地址之后,User-Agent(一般指浏览器)会以一个随机端口(1024<端口<65535)向服务器的WEB程序(常用的有httpd,nginx)等的80端口。这个连接请求(原始的http请求经过TCP/IP 4层模型的层层封包)到达服务器端后(这中间有各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的TCP/IP协议栈(用于识别连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终达到WEB程序,最终建立了TCP/IP的连接。

发起HTTP请求(建立连接后)

HTTP请求报文由三部分组成:请求行,请求头、空行 / 请求正文

**请求行:**用于描述客户端的请求方式(GET/POST等),请求的资源名称(URL)以及使用的HTTP协议的版本号

**请求头:**用于描述客户端请求哪台主机及其端口,以及客户端的一些环境信息等

**空行:**空行就是\r\n (POST请求时候有)

**请求正文:**当使用POST等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中(GET方式是保存在url地址后面,不会放到这里)

举例:

GET请求

下面是浏览器对 http://localhost:8081/test?name=XXG&age=23的GET 请求时发送给服务器的数据:

y1rj7zwfal.png

可以看出请求包含请求行和请求头两部分。其中请求行中包含 method(例如 GET、POST)、URI(通一资源标志符)和协议版本三部分,三个部分之间以空格分开。请求行和每个请求头各占一行,以换行符 CRLF(即 \r\n)分割。

POST请求

下面是浏览器对 http://localhost:8081/test 的 POST 请求时发送给服务器的数据,消息体中带上参数 name=XXG&age=23

7x1vv3oqro.png

可以看出,上面的请求包含三个部分:请求行、请求头、空格/消息体,比之前的 GET 请求多了一个请求消息,其中 请求头和消息体之间用一个空行分割。POST 请求的参数不在 URL 中,而是在消息体中,请求头中多了一项 Content-Length 用于表示消息体的字节数,这样服务器才能知道请求是否发送结束。这也就是 GET 请求和 POST 请求的主要区别。

那么起始行中的请求方法有哪些种呢?

GET: 完整请求一个资源 (常用) HEAD: 仅请求响应首部 POST:提交表单 (常用) PUT: (webdav) 上传文件(但是浏览器不支持该方法) DELETE:(webdav) 删除 OPTIONS:返回请求的资源所支持的方法的方法 TRACE: 追求一个资源请求中间所经过的代理(该方法不能由浏览器发出)

那什么是URL、URI、URN?

URI Uniform Resource Identifier 统一资源标识符 URL Uniform Resource Locator 统一资源定位符 URN Uniform Resource Name 统一资源名称

URL和URN 都属于 URI,为了方便就把URL和URI暂时都通指一个东西

服务器响应http请求,浏览器得到html代码

HTTP响应也由三部分组成:状态行,响应头,空格,消息体

状态行包括:协议版本、状态码、状态码描述

**状态码:**状态码用于表示服务器对请求的处理结果

1xx:指示信息——表示请求已经接受,继续处理 2xx:成功——表示请求已经被成功接收、理解、接受。 3xx:重定向——要完成请求必须进行更进一步的操作 4xx:客户端错误——请求有语法错误或请求无法实现 5xx:服务器端错误——服务器未能实现合法的请求。

列举几种常见的:

200(没有问题) 302(要你去找别人) 304(要你去拿缓存) 307(要你去拿缓存) 403(有这个资源,但是没有访问权限) 404(服务器没有这个资源) 500(服务器这边有问题)

**响应头:**响应头用于描述服务器的基本信息,以及客户端如何处理数据

**空格:**CRLF(即 \r\n)分割

**消息体:**服务器返回给客户端的数据

响应格式如下图

vpa3scd2ea.png

上面的 HTTP 响应中,响应头中的 Content-Length 同样用于表示消息体的字节数。Content-Type 表示消息体的类型,通常浏览网页其类型是HTML,当然还会有其他类型,比如图片、视频等。

浏览器解析html代码,并请求html代码中的资源

浏览器拿到html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载(会使用多线程下载,每个浏览器的线程数不一样),这是时候就用上 keep-alive特性了,建立一次HTTP连接,可以请求多个资源,下载资源的顺序就是按照代码里面的顺序,但是由于每个资源大小不一样,而浏览器又是多线程请求请求资源,所以这里显示的顺序并不一定是代码里面的顺序。

浏览器对页面进行渲染呈现给用户

最后,浏览器利用自己内部的工作机制,把请求的静态资源和html代码进行渲染,渲染之后呈现给用户,浏览器是一个边解析边渲染的过程。

首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。

这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。

DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。

页面在首次加载时必然会经历reflow和repain。

reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少reflow和repain。

JS的解析是由浏览器中的JS解析引擎完成的。

JS是单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载。 ​

浏览器解析html流程如下图 image.png

服务器关闭关闭TCP连接

一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码:

Connection:keep-alive

TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

以上流程就是一次完整的HTTP服务过程.

面试题点

  1. 地址栏输入url开始, 域名到ip的过程
  2. 拿到ip, 开始建立http请求
  3. 拿到html之后的浏览器的渲染过程

回答思路

先说从url到拿到html的过程,然后重点阐述html的渲染过程。之后面试官再次提问的侧重回答(如:重排,重绘、tcp 三次握手四次挥手)。

相关扩展

  1. tcp 三次握手四次挥手

2. http缓存控制

面试问题分析

Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。

浏览器缓存也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。这里我们只讨论 HTTP 缓存相关内容。

在具体了解 HTTP 缓存之前先来明确几个术语:

  • 缓存命中率:从缓存中得到数据的请求数与所有请求数的比率。理想状态是越高越好。
  • 过期内容:超过设置的有效时间,被标记为“陈旧”的内容。通常过期内容不能用于回复客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否仍然准备。
  • 验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
  • 失效:失效就是把内容从缓存中移除。当内容发生改变时就必须移除失效的内容。

浏览器缓存主要是 HTTP 协议定义的缓存机制。HTML meta 标签,例如 含义是让浏览器不缓存当前页面。但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。

浏览器缓存分类

浏览器缓存分为强缓存和协商缓存,浏览器加载一个页面的简单流程如下:

  1. 浏览器先根据这个资源的http头信息来判断是否命中强缓存。如果命中则直接加在缓存中的资源,并不会将请求发送到服务器。(强缓存)
  2. 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器。服务器来判断浏览器本地缓存是否失效。若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源。(协商缓存)
  3. 如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存。(新的请求)
1. 强缓存

命中强缓存时,浏览器并不会将请求发送给服务器。在Chrome的开发者工具中看到http的返回码是200,但是在Size列会显示为(from cache)。

940884-20180423141536107-329179455.png

强缓存是利用http的返回头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间。

Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。但在上面我们提到过,cache-control的优先级更高。 Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

940884-20180423141609527-358000355.png

该字段会返回一个时间,比如Expires:Thu,31 Dec 2037 23:59:59 GMT。这个时间代表着这个资源的失效时间,也就是说在2037年12月31日23点59分59秒之前都是有效的,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端时间偏差变大以后,就会导致缓存混乱。于是发展出了Cache-Control。

Cache-Control

Cache-Control是一个相对时间,例如Cache-Control:3600,代表着资源的有效期是3600秒。由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。 Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。

Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

  1. max-age 指定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 * 60)天,第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。 940884-20180423141638673-1917674992.png

在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

  1. s-maxage 同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。
  2. public 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。
  3. private 表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。
  4. no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存。
  5. no-store 禁止缓存,每次请求都要向服务器重新获取数据。

7.must-revalidate指定如果页面是过期的,则去服务器进行获取。这个指令并不常用,就不做过多的讨论了。 强缓存流程图 image.png

2. 协商缓存

若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据http头信息中的Last-Modify/If-Modify-Since或Etag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回码为304,浏览器从缓存中加载资源。

Last-Modify/If-Modify-Since

浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT

940884-20180423141852114-1757065670.png

当浏览器再次请求该资源时,发送的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。

940884-20180423141732879-1484228353.png

如果命中缓存,则返回http304,并且不会返回资源内容,并且不会返回Last-Modify。由于对比的服务端时间,所以客户端与服务端时间差距不会导致问题。但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致)。于是出现了ETag/If-None-Match。

ETag/If-None-Match

与Last-Modify/If-Modify-Since不同的是,Etag/If-None-Match返回的是一个校验码(ETag: entity tag)。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化*。ETag值的变更则说明资源状态已经被修改。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。 940884-20180423141918779-1206116367.png

ETag扩展说明

我们对ETag寄予厚望,希望它对于每一个url生成唯一的值,资源变化时ETag也发生变化。神秘的Etag是如何生成的呢?以Apache为例,ETag生成靠以下几种因子

  1. 文件的i-node编号,此i-node非彼iNode。是Linux/Unix用来识别文件的编号。是的,识别文件用的不是文件名。使用命令’ls –I’可以看到。
  2. 文件最后修改时间
  3. 文件大小 生成Etag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。所以,理论上ETag也是会重复的,只是概率小到可以忽略。

既生Last-Modified何生Etag?

你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  1. Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
  2. 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

3.有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

浏览器第一次请求

940884-20180423141945261-83532090.png

浏览器第二次请求

940884-20180423141951735-912699213.png

面试题点

  1. http缓存作用范围

http缓存能够帮助服务器提高并发性能,很多资源不需要重复请求直接从浏览器中拿缓存

  1. http缓存分类

强缓存 协商缓存

  1. http缓存实现技术

强缓存: 通过 expires 和 cache-control控制
协商缓存: 通过 last-Modify 和E-tag控制

其他:

  1. 为什么有expires 有需要cache-control

    因为expires 有个服务器和浏览器时间不同步的问题
    expires是绝对事件   cache-control是相对时间
    
  2. last-modify和Etag last-modify 它是有个精度问题 到秒 e-tag 没有精度问题 只要文件改变 e-tag值就改变

回答思路

首先回答http缓存的作用范围, 然后点出http缓存主要分为强缓存和协商缓存。最后重点阐述强缓存和协商缓存的配置实现和相关http响应头字段的用法。

相关扩展

  1. 用户行为与缓存 浏览器缓存行为还有用户的行为有关!!! (https://img-blog.csdnimg.cn/af29341b998546f7976d605e969129ab.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5YmN56uv5bKa5p6r,size_20,color_FFFFFF,t_70,g_se,x_16)

  2. 服务器端的缓存 CDN 、redis、数据库缓存等

  3. Nginx下关于缓存控制字段cache-control的配置说明