大家好,我是前端岚楓,一枚二線城市的程式媛,今天主要跟大家分享我整理的筆記2021前端面試題系列:HTTP請求和HTTP緩存控制,此方面内容在我們的工作中常用到, 也是面試官經常提問的問題,希望下面文章對大家有所幫助。
1. 一次完整的HTTP服務過程
問題分析
當我們在web浏覽器的位址欄中輸入:
www.baidu.com
,具體發生了什麼?
- 對
這個網址進行DNS域名解析,得到對應的IP位址www.baidu.com
- 根據這個IP,找到對應的伺服器,發起TCP的三次握手
- 建立TCP連接配接後發起HTTP請求
- 伺服器響應HTTP請求,浏覽器得到html代碼
- 浏覽器解析html代碼,并請求html代碼中的資源(如js、css、圖檔等)(先得到html代碼,才能去找這些資源)
- 浏覽器對頁面進行渲染呈現給使用者
- 伺服器關閉關閉TCP連接配接
注:
- DNS怎麼找到域名的?
DNS域名解析采用的是遞歸查詢的方式,過程是,先去找DNS緩存->緩存找不到就去找根域名伺服器->根域名又會去找下一級,這樣遞歸查找之後,找到了,給我們的web浏覽器
- 為什麼HTTP協定要基于TCP來實作?
TCP是一個端到端的可靠的面相連接配接的協定,HTTP基于傳輸層TCP協定不用擔心資料傳輸的各種問題(當發生錯誤時,會重傳)
- 最後一步浏覽器是如何對頁面進行渲染的?
a)解析html檔案構成 DOM樹
b)解析CSS檔案構成渲染樹
c)邊解析,邊渲染
d)JS 單線程運作,JS有可能修改DOM結構,意味着JS執行完成前,後續所有資源的下載下傳是沒有必要的,是以JS是單線程,會阻塞後續資源下載下傳
各個步驟具體細節
DNS解析(域名解析伺服器)
- 首先會搜尋浏覽器自身的DNS緩存(緩存時間比較短,大概隻有1分鐘,且隻能容納1000條緩存)
- 如果浏覽器自身的緩存裡面沒有找到,那麼浏覽器會搜尋系統自身的DNS緩存
- 如果還沒有找到,那麼嘗試從 hosts檔案裡面去找
- 在前面三個過程都沒擷取到的情況下,就遞歸地去域名伺服器去查找,具體過程如下
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服務過程.
面試題點
- 位址欄輸入url開始, 域名到ip的過程
- 拿到ip, 開始建立http請求
- 拿到html之後的浏覽器的渲染過程
回答思路
先說從url到拿到html的過程,然後重點闡述html的渲染過程。之後面試官再次提問的側重回答(如:重排,重繪、tcp 三次握手四次揮手)。
相關擴充
- tcp 三次握手四次揮手
2. http緩存控制
面試問題分析
Web 緩存大緻可以分為:資料庫緩存、伺服器端緩存(代理伺服器緩存、CDN 緩存)、浏覽器緩存。
浏覽器緩存也包含很多内容: HTTP 緩存、indexDB、cookie、localstorage 等等。這裡我們隻讨論 HTTP 緩存相關内容。
在具體了解 HTTP 緩存之前先來明确幾個術語:
- 緩存命中率:從緩存中得到資料的請求數與所有請求數的比率。理想狀态是越高越好。
- 過期内容:超過設定的有效時間,被标記為“陳舊”的内容。通常過期内容不能用于回複用戶端的請求,必須重新向源伺服器請求新的内容或者驗證緩存的内容是否仍然準備。
- 驗證:驗證緩存中的過期内容是否仍然有效,驗證通過的話重新整理過期時間。
- 失效:失效就是把内容從緩存中移除。當内容發生改變時就必須移除失效的内容。
浏覽器緩存主要是 HTTP 協定定義的緩存機制。HTML meta 标簽,例如
含義是讓浏覽器不緩存目前頁面。但是代理伺服器不解析 HTML 内容,一般應用廣泛的是用 HTTP 頭資訊控制緩存。
浏覽器緩存分類
浏覽器緩存分為強緩存和協商緩存,浏覽器加載一個頁面的簡單流程如下:
- 浏覽器先根據這個資源的http頭資訊來判斷是否命中強緩存。如果命中則直接加在緩存中的資源,并不會将請求發送到伺服器。(強緩存)
- 如果未命中強緩存,則浏覽器會将資源加載請求發送到伺服器。伺服器來判斷浏覽器本地緩存是否失效。若可以使用,則伺服器并不會傳回資源資訊,浏覽器繼續從緩存加載資源。(協商緩存)
- 如果未命中協商緩存,則伺服器會将完整的資源傳回給浏覽器,浏覽器加載新資源,并更新緩存。(新的請求)
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 可以由多個字段組合而成,主要有以下幾個取值:
- max-age指定一個時間長度,在這個時間段内緩存是有效的,機關是s。例如設定 Cache-Control:max-age=31536000,也就是說緩存有效期為(31536000 / 24 / 60 * 60)天,第一次通路這個資源的時候,伺服器端也傳回了 Expires 字段,并且過期時間是一年後。
在沒有禁用緩存并且沒有超過有效時間的情況下,再次通路這個資源就命中了緩存,不會向伺服器請求資源而是直接從浏覽器緩存中取。
在沒有禁用緩存并且沒有超過有效時間的情況下,再次通路這個資源就命中了緩存,不會向伺服器請求資源而是直接從浏覽器緩存中取。
- s-maxage同 max-age,覆寫 max-age、Expires,但僅适用于共享緩存,在私有緩存中被忽略。
- public表明響應可以被任何對象(發送請求的用戶端、代理伺服器等等)緩存。
- private表明響應隻能被單個使用者(可能是作業系統使用者、浏覽器使用者)緩存,是非共享的,不能被代理伺服器緩存。
- no-cache強制所有緩存了該響應的使用者,在使用已緩存的資料前,發送帶驗證器的請求到伺服器。不是字面意思上的不緩存。
- 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生成靠以下幾種因子
- 檔案的i-node編号,此i-node非彼iNode。是Linux/Unix用來識别檔案的編号。是的,識别檔案用的不是檔案名。使用指令’ls –I’可以看到。
- 檔案最後修改時間
-
檔案大小
生成Etag的時候,可以使用其中一種或幾種因子,使用抗碰撞散列函數來生成。是以,理論上ETag也是會重複的,隻是機率小到可以忽略。
既生Last-Modified何生Etag?
你可能會覺得使用Last-Modified已經足以讓浏覽器知道本地的緩存副本是否足夠新,為什麼還需要Etag(實體辨別)呢?HTTP1.1中Etag的出現主要是為了解決幾個Last-Modified比較難解決的問題:
- Last-Modified标注的最後修改隻能精确到秒級,如果某些檔案在1秒鐘以内,被修改多次的話,它将不能準确标注檔案的修改時間
- 如果某些檔案會被定期生成,當有時内容并沒有任何變化,但Last-Modified卻改變了,導緻檔案沒法使用緩存
3.有可能存在伺服器沒有準确擷取檔案修改時間,或者與代理伺服器時間不一緻等情形
Etag是伺服器自動生成或者由開發者生成的對應資源在伺服器端的唯一辨別符,能夠更加準确的控制緩存。Last-Modified與ETag是可以一起使用的,伺服器會優先驗證ETag,一緻的情況下,才會繼續比對Last-Modified,最後才決定是否傳回304。
浏覽器第一次請求
浏覽器第二次請求
面試題點
- http緩存作用範圍
http緩存能夠幫助伺服器提高并發性能,很多資源不需要重複請求直接從浏覽器中拿緩存
- http緩存分類
強緩存 協商緩存
- http緩存實作技術
強緩存: 通過 expires 和 cache-control控制
協商緩存: 通過 last-Modify 和E-tag控制
其他:
- 為什麼有expires 有需要cache-control
因為expires 有個伺服器和浏覽器時間不同步的問題
expires是絕對事件 cache-control是相對時間
-
last-modify和Etag
last-modify 它是有個精度問題 到秒
e-tag 沒有精度問題 隻要檔案改變 e-tag值就改變
回答思路
首先回答http緩存的作用範圍, 然後點出http緩存主要分為強緩存和協商緩存。最後重點闡述強緩存和協商緩存的配置實作和相關http響應頭字段的用法。
相關擴充
- 使用者行為與緩存 浏覽器緩存行為還有使用者的行為有關!!!
- 伺服器端的緩存 CDN 、redis、資料庫緩存等
- Nginx下關于緩存控制字段cache-control的配置說明