2021前端面試題系列:HTTP請求和HTTP緩存控制

大家好,我是前端嵐楓,一枚二線城市的程序媛,今天主要跟大家分享我整理的筆記2021前端面試題系列:HTTP請求和HTTP緩存控制,此方面內容在我們的工作中常用到, 也是面試官經常提問的問題,希望下面文章對大家有所幫助。

1. 一次完整的HTTP服務過程

問題分析

當我們在web瀏覽器的地址欄中輸入:www.xxx.com,具體發生了什麼?

  1. www.xxx.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解析(域名解析服務器)

  1. 首先會搜索瀏覽器自身的DNS緩存(緩存時間比較短,大概只有1分鐘,且只能容納1000條緩存)
  2. 如果瀏覽器自身的緩存裏面沒有找到,那麼瀏覽器會搜索系統自身的DNS緩存
  3. 如果還沒有找到,那麼嘗試從 hosts文件裏面去找
  4. 在前面三個過程都沒獲取到的情況下,就遞歸地去域名服務器去查找,具體過程如下

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 請求時發送給服務器的數據:

可以看出請求包含請求行和請求頭兩部分。其中請求行中包含 method(例如 GET、POST)、URI(通一資源標誌符)和協議版本三部分,三個部分之間以空格分開。請求行和每個請求頭各佔一行,以換行符 CRLF(即 \r\n)分割。

POST請求

下面是瀏覽器對 http://localhost:8081/test 的 POST 請求時發送給服務器的數據,消息體中帶上參數 name=XXG&age=23

可以看出,上面的請求包含三個部分:請求行、請求頭、空格/消息體,比之前的 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)分割

消息體:服務器返回給客戶端的數據

響應格式如下圖

上面的 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流程如下圖

服務器關閉關閉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)。

強緩存是利用http的返回頭中的Expires或者Cache-Control兩個字段來控制的,用來表示資源的緩存時間。

Expires

緩存過期時間,用來指定資源到期的時間,是服務器端的具體的時間點。也就是說,Expires=max-age + 請求時間,需要和Last-modified結合使用。但在上面我們提到過,cache-control的優先級更高。 Expires是Web服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求。

該字段會返回一個時間,比如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 字段,並且過期時間是一年後。

在沒有禁用緩存並且沒有超過有效時間的情況下,再次訪問這個資源就命中了緩存,不會向服務器請求資源而是直接從瀏覽器緩存中取。

在沒有禁用緩存並且沒有超過有效時間的情況下,再次訪問這個資源就命中了緩存,不會向服務器請求資源而是直接從瀏覽器緩存中取。

  1. s-maxage 同 max-age,覆蓋 max-age、Expires,但僅適用於共享緩存,在私有緩存中被忽略。
  2. public 表明響應可以被任何對象(發送請求的客戶端、代理服務器等等)緩存。
  3. private 表明響應只能被單個用戶(可能是操作系統用戶、瀏覽器用戶)緩存,是非共享的,不能被代理服務器緩存。
  4. no-cache 強制所有緩存了該響應的用戶,在使用已緩存的數據前,發送帶驗證器的請求到服務器。不是字面意思上的不緩存。
  5. no-store 禁止緩存,每次請求都要向服務器重新獲取數據。

7.must-revalidate指定如果頁面是過期的,則去服務器進行獲取。這個指令並不常用,就不做過多的討論了。
強緩存流程圖

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

當瀏覽器再次請求該資源時,發送的請求頭中會包含If-Modify-Since,該值爲緩存之前返回的Last-Modify。服務器收到If-Modify-Since後,根據資源的最後修改時間判斷是否命中緩存。

如果命中緩存,則返回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值來判斷是否命中緩存。


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。

瀏覽器第一次請求

瀏覽器第二次請求

面試題點

  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. 用戶行爲與緩存 瀏覽器緩存行爲還有用戶的行爲有關!!!


  2. 服務器端的緩存 CDN 、redis、數據庫緩存等

  3. Nginx下關於緩存控制字段cache-control的配置說明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章