博客原文地址: https://finget.github.io/view...
瀏覽器是多進程的,主要分爲:javascript
這裏咱們只考慮輸入的是一個URL 結構字符串,若是是非 URL 結構的字符串,則會用瀏覽器默認的搜索引擎搜索該字符串。css
URL 主要由 協議
、主機
、端口
、路徑
、查詢參數
、錨點
6部分組成!html
輸入URL後,瀏覽器會解析出協議、主機、端口、路徑等信息,並構造一個HTTP請求。前端
expires
和cache-control
判斷是否命中(包括是否過時)強緩存策略,若是命中,直接從緩存獲取資源,並不會發送請求。若是沒有命中,則進入下一步。last-modified
和etag
判斷是否命中協商緩存,若是命中,直接從緩存獲取資源。若是沒有命中,則進入下一步。因爲安全隱患,會使用 HSTS 強制客戶端使用 HTTPS 訪問頁面。詳見:你所不知道的 HSTS。
當你的網站均採用 HTTPS,並符合它的安全規範,就能夠申請加入 HSTS 列表,以後用戶不加 HTTPS 協議再去訪問你的網站,瀏覽器都會定向到 HTTPS。不管匹配到沒有,都要開始 DNS 查詢工做了。java
強制緩存就是向瀏覽器緩存查找該請求結果,並根據該結果的緩存規則來決定是否使用該緩存結果的過程。強緩存又分爲兩種Expires
和Cache-Control
nginx
請求頭:git
字段名稱 | 說明 |
---|---|
no-cache | 告知(代理)服務器不直接使用緩存,要求向原服務器發起請求 |
no-store | 全部內容都不會被保存到緩存或Internet臨時文件中 |
max-age=delta-seconds | 告知服務器客戶端但願接收一個存在時間不大於delta-secconds秒的資源 |
max-stale[=delta-seconds] | 告知(代理)服務器客戶端願意接收一個超過緩存時間的資源,如有定義delta-seconds則爲delta-seconds秒,若沒有則爲任意超出時間 |
min-fresh=delta-seconds | 告知(代理)服務器客戶端但願接收一個在小於delta-seconds秒內被更新過的資源 |
no-transform | 告知(代理)服務器客戶端但願獲取實體數據沒有被轉換(好比壓縮)過的資源 |
noly-if-cached | 告知(代理)服務器客戶端但願獲取緩存的內容(如有),而不用向原服務器發去請求 |
cache-extension | 自定義擴展值,若服務器不識別該值將被忽略掉 |
響應頭:github
字段名稱 | 說明 |
---|---|
public | 代表任何狀況下都得緩存該資源(即便是須要HTTP認證的資源) |
Private=[field-name] | 代表返回報文中所有或部分(若指定了field-name則爲field-name的字段數據)僅開放給某些用戶(服務器指定的share-user,如代理服務器)作緩存使用,其餘用戶則不能緩存這些數據 |
no-cache | 不直接使用緩存,要求向服務器發起(新鮮度校驗)請求 |
no-store | 因此內容都不會被保存到緩存或Internet臨時文件中 |
no-transform | 告知客戶端緩存文件時不得對實體數據作任何改變 |
noly-if-cached | 告知(代理)服務器客戶端但願獲取緩存的內容(如有),而不用向原服務器發去請求 |
must-revalidate | 當前資源必定是向原方法服務器發去驗證請求的,如請求是吧會返回504(而非代理服務器上的緩存) |
proxy-revalidate | 與must-revalidate相似,但僅能應用於共享緩存(如代理) |
max-age=delta-seconds | 告知客戶端該資源在delta-seconds秒內是新鮮的,無需向服務器發請求 |
s-maxage=delta-seconds | 同max-age,但僅能應用於共享緩存(如代理) |
cache-extension | 自定義擴展值,若服務器不識別該值將被忽略掉 |
示例:web
// server.js const http = require('http') const fs = require('fs') http.createServer(function (request, response) { console.log('request come', request.url) if (request.url === '/') { const html = fs.readFileSync('test.html', 'utf8') response.writeHead(200, { 'Content-Type': 'text/html' }) response.end(html) } if (request.url === '/script.js') { response.writeHead(200, { 'Content-Type': 'text/javascript', 'Cache-Control': 'max-age=20,public' // 緩存20s 多個值用逗號分開 }) response.end('console.log("script loaded")') } }).listen(8888) console.log('server listening on 8888')
// test.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> </body> <script src="/script.js"></script> </html>
協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程。面試
在瀏覽器第一次給服務器發送請求後,服務器會在響應頭中加上這個字段。
瀏覽器接收到後,若是再次請求,會在請求頭中攜帶If-Modified-Since
字段,這個字段的值也就是服務器傳來的最後修改時間。
服務器拿到請求頭中的If-Modified-Since
的字段後,其實會和這個服務器中該資源的最後修改時間Last-Modified
對比,詢問服務器在該日期後資源是否有更新,有更新的話就會將新的資源發送回來。
可是若是在本地打開緩存文件,就會形成 Last-Modified
被修改,因此在 HTTP / 1.1
出現了 ETag
。
ETag
是服務器根據當前文件的內容,給文件生成的惟一標識,只要裏面的內容有改動,這個值就會變。服務器經過響應頭把這個值給瀏覽器。
瀏覽器接收到ETag的值,會在下次請求時,將這個值做爲If-None-Match
這個字段的內容,並放到請求頭中,而後發給服務器。
若是兩種方式都支持的話,服務器會優先考慮ETag
Service Worker
是運行在瀏覽器背後的獨立線程,通常能夠用來實現緩存功能。使用 Service Worker
的話,傳輸協議必須爲 HTTPS
。由於 Service Worker
中涉及到請求攔截,因此必須使用 HTTPS
協議來保障安全。Service Worker
的緩存與瀏覽器其餘內建的緩存機制不一樣,它可讓咱們自由控制緩存哪些文件、如何匹配緩存、如何讀取緩存,而且緩存是持續性的。
Service Worker
實現緩存功能通常分爲三個步驟:首先須要先註冊 Service Worker
,而後監聽到 install
事件之後就能夠緩存須要的文件,那麼在下次用戶訪問的時候就能夠經過攔截請求的方式查詢是否存在緩存,存在緩存的話就能夠直接讀取緩存文件,不然就去請求數據。
當 Service Worker
沒有命中緩存的時候,咱們須要去調用 fetch
函數獲取數據。也就是說,若是咱們沒有在 Service Worker
命中緩存的話,會根據緩存查找優先級去查找數據。可是無論咱們是從 Memory Cache
中仍是從網絡請求中獲取的數據,瀏覽器都會顯示咱們是從 Service Worker
中獲取的內容。
Memory Cache
也就是內存中的緩存,主要包含的是當前中頁面中已經抓取到的資源,例如頁面上已經下載的樣式、腳本、圖片等。讀取內存中的數據確定比磁盤快,內存緩存雖然讀取高效,但是緩存持續性很短,會隨着進程的釋放而釋放。 一旦咱們關閉Tab頁面,內存中的緩存也就被釋放了。
那麼既然內存緩存這麼高效,咱們是否是能讓數據都存放在內存中呢?
這是不可能的。計算機中的內存必定比硬盤容量小得多,操做系統須要精打細算內存的使用,因此能讓咱們使用的內存必然很少。
須要注意的事情是,內存緩存在緩存資源時並不關心返回資源的HTTP緩存頭Cache-Control
是什麼值,同時資源的匹配也並不是僅僅是對URL作匹配,還可能會對Content-Type
,CORS等其餘特徵作校驗。
Disk Cache
也就是存儲在硬盤中的緩存,讀取速度慢點,可是什麼都能存儲到磁盤中,比之 Memory Cache
勝在容量和存儲時效性上。
Push Cache
(推送緩存)是 HTTP/2
中的內容,當以上三種緩存都沒有命中時,它纔會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,而且緩存時間也很短暫,在Chrome瀏覽器中只有5分鐘左右,同時它也並不是嚴格執行HTTP頭中的緩存指令。
Edge
和 Safari
瀏覽器支持相對比較差no-cache
和 no-store
的資源Push Cache
就被釋放HTTP/2
的鏈接,也就可使用同一個Push Cache
。這主要仍是依賴瀏覽器的實現而定,出於對性能的考慮,有的瀏覽器會對相同域名但不一樣的tab標籤使用同一個HTTP鏈接。Push Cache
中的緩存只能被使用一次在發起http請求以前,瀏覽器首先要作去得到咱們想訪問網頁的IP地址,瀏覽器會發送一個UDP的包給DNS域名解析服務器。
咱們的瀏覽器、操做系統、路由器都會緩存一些URL對應的IP地址,統稱爲DNS高速緩存。這是爲了加快DNS解析速度,使得沒必要每次都到根域名服務器中去查詢。
迭代查詢的方式就是,局部的DNS服務器並不會本身向其餘服務器進行查詢,而是把可以解析該域名的服務器IP地址返回給客戶端,客戶端會不斷的向這些服務器進行查詢,直到查詢到了位置,迭代的話只會幫你找到相關的服務器,而後說我如今比較忙,你本身去找吧。
DNS還有負載均衡的做用,如今不少網站都有多個服務器,當一個網站訪問量過大的時候,若是全部請求都請求在同一個服務器上,可能服務器就會崩掉,這時候就用到了DNS負載均衡技術,當一個網站有多個服務器地址時,在應答DNS查詢的時候,DNS服務器會對每一個查詢返回不一樣的解析結果,也就是返回不一樣的IP地址,從而把訪問引導到不一樣的服務器上去,來達到負載均衡的目的。例如能夠根據每臺機器的負載量,或者該機器距離用戶的地理位置距離等等條件。
TCP(Transmission Control Protocol)傳輸控制協議。
TCP/IP協議將應用層、表示層、會話層合併爲應用層,物理層和數據鏈路層合併爲網絡接口層。
TCP/IP協議不只僅指的是TCP和IP兩個協議,⽽是指的⼀個由FTP,SMTP,TCP,UDP,IP,ARP等等協議構成的協議集合。
客服端和服務端在進行http請求和返回的工程中,須要建立一個TCP connection
(由客戶端發起),http
不存在鏈接這個概念,它只有請求和響應。請求和響應都是數據包,它們之間的傳輸通道就是TCP connection
。
位碼即tcp標誌位,有6種標示:
第一次握手:主機A發送位碼爲SYN=1
,隨機產生Seq number=1234567
的數據包到服務器,主機B由SYN=1
知道,A要求創建聯機;(第一次握手,由瀏覽器發起,告訴服務器我要發送請求了)
第二次握手:主機B收到請求後要確認聯機信息,向A發送ack number=(主機A的seq+1)
,SUN=1,ACK=1234567 + 1
,隨機產生Seq=7654321
的包;(第二次握手,由服務器發起,告訴瀏覽器我準備接受了,你趕忙發送吧)
第三次握手:主機A收到後檢查ack number
是否正確,即第一次發送的seq number+1
,以及位碼SYN
是否爲1,若正確,主機A會再發送ack number=(主機B的seq+1)
,ack=7654321 + 1
,主機B收到後確認Seq
值與ACK=7654321+ 1
則鏈接創建成功;(第三次握手,由瀏覽器發送,告訴服務器,我立刻就發了,準備接受吧)
老是要問:爲何須要三次握手,兩次不行嗎?其實這是由TCP的自身特色 可靠傳輸決定的。客戶端和服務端要進行可靠傳輸,那麼就須要 確認雙方的接收
和發送
能力。第一次握手能夠確認客服端的發送能力
,第二次握手,服務端SYN=1,Seq=Y
就確認了發送能力
,ACK=X+1
就確認了接收能力
,因此第三次握手才能夠確認客戶端的接收能力
。否則容易出現丟包的現象。
試想若是是用兩次握手,則會出現下面這種狀況:
如客戶端發出鏈接請求,但因鏈接請求報文丟失而未收到確認,因而客戶端再重傳一次鏈接請求。後來收到了確認,創建了鏈接。數據傳輸完畢後,就釋放了鏈接,客戶端共發出了兩個鏈接請求報文段,其中第一個丟失,第二個到達了服務端,可是第一個丟失的報文段只是在某些網絡結點長時間滯留了,延誤到鏈接釋放之後的某個時間纔到達服務端,此時服務端誤認爲客戶端又發出一次新的鏈接請求,因而就向客戶端發出確認報文段,贊成創建鏈接,不採用三次握手,只要服務端發出確認,就創建新的鏈接了,此時客戶端忽略服務端發來的確認,也不發送數據,則服務端一致等待客戶端發送數據,浪費資源。
服務器第一次收到客戶端的 SYN 以後,就會處於 SYN_RCVD 狀態,此時雙方尚未徹底創建其鏈接,服務器會把此種狀態下請求鏈接放在一個隊列裏,咱們把這種隊列稱之爲半鏈接隊列。
固然還有一個全鏈接隊列,就是已經完成三次握手,創建起鏈接的就會放在全鏈接隊列中。若是隊列滿了就有可能會出現丟包現象。
這裏在補充一點關於SYN-ACK 重傳次數的問題:
服務器發送完SYN-ACK包,若是未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳。若是重傳次數超過系統規定的最大重傳次數,系統將該鏈接信息從半鏈接隊列中刪除。
注意,每次重傳等待的時間不必定相同,通常會是指數增加,例如間隔時間爲 1s,2s,4s,8s…
當一端爲創建鏈接而發送它的SYN時,它爲鏈接選擇一個初始序號。ISN隨時間而變化,所以每一個鏈接都將具備不一樣的ISN。ISN能夠看做是一個32比特的計數器,每4ms加1 。這樣選擇序號的目的在於防止在網絡中被延遲的分組在之後又被傳送,而致使某個鏈接的一方對它作錯誤的解釋。
三次握手的其中一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number),以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。若是 ISN 是固定的,攻擊者很容易猜出後續的確認號,所以 ISN 是動態生成的。
其實第三次握手的時候,是能夠攜帶數據的。可是,第一次、第二次握手不能夠攜帶數據。
爲何這樣呢?你們能夠想一個問題,假如第一次握手能夠攜帶數據的話,若是有人要惡意攻擊服務器,那他每次都在第一次握手中的 SYN 報文中放入大量的數據。由於攻擊者根本就不理服務器的接收、發送能力是否正常,而後瘋狂着重複發 SYN 報文的話,這會讓服務器花費不少時間、內存空間來接收這些報文。
也就是說,第一次握手不能夠放數據,其中一個簡單的緣由就是會讓服務器更加容易受到攻擊了。而對於第三次的話,此時客戶端已經處於 ESTABLISHED 狀態。對於客戶端來講,他已經創建起鏈接了,而且也已經知道服務器的接收、發送能力是正常的了,因此能攜帶數據也沒啥毛病。
服務器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,因此服務器容易受到SYN洪泛攻擊。SYN攻擊就是Client在短期內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server則回覆確認包,並等待Client確認,因爲源地址不存在,所以Server須要不斷重發直至超時,這些僞造的SYN包將長時間佔用未鏈接隊列,致使正常的SYN請求由於隊列滿而被丟棄,從而引發網絡擁塞甚至系統癱瘓。SYN 攻擊是一種典型的 DoS/DDoS 攻擊。
檢測 SYN 攻擊很是的方便,當你在服務器上看到大量的半鏈接狀態時,特別是源IP地址是隨機的,基本上能夠判定這是一次SYN攻擊。在 Linux/Unix 上可使用系統自帶的 netstats 命令來檢測 SYN 攻擊。
netstat -n -p TCP | grep SYN_RECV
常見的防護 SYN 攻擊的方法有以下幾種:
HTTP/0.9
HTTP/1.0
status code
和 header
HTTP/1.1
GET
, HEAD
, POST
, PUT
,DELETE
, TRACE
, OPTIONS
HTTP2
HTTP3
HTTP3 的主要改進在傳輸層上。傳輸層不會再有我前面提到的那些繁重的 TCP 鏈接了。如今,一切都會走 UDP。
如今 HTTP3 最快!
請求報文:
響應報文:
- TCP是底層通信協議,定義的是數據傳輸和鏈接方式的規範;
關於HTTP的東西還有不少,我在最後放了張 大圖。
在HTTP的基礎上再加一層TLS(傳輸層安全性協議)或者SSL(安全套接層),就構成了HTTPS協議。
HTTPS 默認工做在 TCP 協議443端口,它的工做流程通常如如下方式:
Client Hello
消息,其中攜帶客戶端支持的協議版本、加密算法、壓縮算法以及客戶端生成的隨機數;服務端收到客戶端支持的協議版本、加密算法等信息後;
Server Hello
消息,並攜帶選擇特定的協議版本、加密方法、會話 ID 以及服務端生成的隨機數;Certificate
消息,即服務端的證書鏈,其中包含證書支持的域名、發行方和有效期等信息;Server Key Exchange
消息,傳遞公鑰以及簽名等信息;Certificate Request
,驗證客戶端的證書;Server Hello Done
消息,通知服務端已經發送了所有的相關信息;客戶端收到服務端的協議版本、加密方法、會話 ID 以及證書等信息後,驗證服務端的證書;
Client Key Exchange
消息,包含使用服務端公鑰加密後的隨機字符串,即預主密鑰(Pre Master Secret
);Change Cipher Spec
消息,通知服務端後面的數據段會加密傳輸;Finished
消息,其中包含加密後的握手信息;服務端收到 Change Cipher Spec
和 Finished
消息後;
Change Cipher Spec
消息,通知客戶端後面的數據段會加密傳輸;Finished
消息,驗證客戶端的 Finished
消息並完成 TLS 握手;TLS 握手的關鍵在於利用通訊雙發生成的隨機字符串和服務端的證書公鑰生成一個雙方通過協商後的對稱密鑰,這樣通訊雙方就可使用這個對稱密鑰在後續的數據傳輸中加密消息數據,防止中間人的監聽和攻擊,保證通信安全。
HTTPS鏈接 須要7次握手,3次TCP + 4次TSL。
每臺服務器上都會安裝處理請求的應用——Web Server。常見的Web Server 產品有 apache
、nginx
、IIS
或 Lighttpd
等。
HTTP請求通常能夠分爲兩類,靜態資源 和 動態資源。
請求訪問靜態資源,這個就直接根據url地址去服務器裏找就行了。
請求動態資源的話,就須要web server把不一樣請求,委託給服務器上處理相應請求的程序進行處理(例如 CGI 腳本,JSP 腳本,servlets,ASP 腳本,服務器端 JavaScript,或者一些其它的服務器端技術等),而後返回後臺程序處理產生的結果做爲響應,發送到客戶端。
服務器在處理請求的時候主要有三種方式:
字節 → 字符 → 令牌 → 節點 → 對象模型。
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div>![](awesome-photo.jpg)</div> </body> </html>
body { font-size: 16px } p { font-weight: bold } span { color: red } p span { display: none } img { float: right }
渲染流程:
<div class="position_">position</div> <div class="box_3d">3d變換</div> <div class="will-change">will-change</div> <div class="transform"></div> <iframe src="https://www.baidu.com"></iframe> div {width: 100px;height: 100px;} .position_ {background: pink;position: fixed;z-index: 20;} .box_3d {background: red;transform: translate3d(100px,30px,10px);} .will-change {background: #f12312;will-change: transform;} .transform {background: #302912;transform: skew(30deg, 20deg);}
在 chrome 上查看 Layers.
若是沒有打開Layers,按下圖打開:
知道圖層的存在,咱們能夠手動打開一個圖層,經過添加
transform: translateZ(0)
這樣迴流和重繪的代價就小了,效率就會大大提升。可是不要濫用這個屬性,不然會大大增長內存消耗。—— 開啓GPU加速。
當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:color、background-color、visibility等),瀏覽器會將新樣式賦予給元素並從新繪製它,這個過程稱爲重繪。
當Render Tree中部分或所有元素的尺寸、結構、或某些屬性發生改變時,瀏覽器從新渲染部分或所有文檔的過程稱爲迴流。
迴流必將引發重繪,而重繪不必定會引發迴流。
引發迴流:
引發迴流的屬性和方法:
由於當服務端收到客戶端的SYN鏈接請求報文後,能夠直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。可是關閉鏈接時,當服務端收到FIN報文時,極可能並不會當即關閉SOCKET,因此只能先回復一個ACK報文,告訴客戶端,「你發的FIN報文我收到了」。只有等到我服務端全部的報文都發送完了,我才能發送FIN報文,所以不能一塊兒發送。故須要四次揮手。
客戶端收到服務端的鏈接釋放報文段後,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入TIME_WAIT(時間等待)狀態。此時TCP未釋放掉,須要通過時間等待計時器設置的時間2MSL後,客戶端才進入CLOSED狀態。若是不等待,客戶端直接跑路,當服務端還有不少數據包要給客戶端發,且還在路上的時候,若客戶端的端口此時恰好被新的應用佔用,那麼就接收到了無用數據包,形成數據包混亂。
理論上,四個報文都發送完畢,就能夠直接進入CLOSE狀態了,可是可能網絡是不可靠的,有可能最後一個ACK丟失。因此TIME_WAIT狀態就是用來重發可能丟失的ACK報文。
1 個 MSL 確保四次揮手中主動關閉方最後的 ACK 報文最終能達到對端;
1 個 MSL 確保對端沒有收到 ACK 重傳的 FIN 報文能夠到達。
若是想要高清大圖或者Xmind文件的話,能夠私信lian x
在這裏對前輩大佬表示敬意,查找了不少資料,若有遺漏,還請見諒。文中若是有誤,還望及時指出,感謝!
5 分鐘看懂 HTTP3
一文帶你瞭解HTTPS
從URL輸入到頁面展示到底發生什麼?
從URL輸入到頁面展示到底發生什麼?
在瀏覽器上請求一個URL的所有過程
前端經典面試題: 從輸入URL到頁面加載發生了什麼?
瀏覽器緩存看這一篇就夠了
從輸入URL到瀏覽器顯示頁面的流程
在瀏覽器輸入 URL 回車以後發生了什麼(超詳細版)
TCP和Http的區別!我都搞懂了,你就別迷糊了!
爲何 HTTPS 須要 7 次握手以及 9 倍時延
渲染樹構建、佈局及繪製
瀏覽器渲染詳細過程:重繪、重排和 composite 只是冰山一角
瀏覽器渲染機制和 Reflow(迴流、重排)和 Repaint(重繪)
問我Chrome瀏覽器的渲染原理(6000字長文)
淺談瀏覽器的圖層與重繪重排(詳細),以及如何用於性能優化
HTTP 筆記 1:Web 基礎及簡單的 HTTP 協議
圖解HTTP-21張圖把HTTP安排得明明白白
HTTP3
一文帶你瞭解HTTPS
瀏覽器工做原理與實踐