從輸入URL開始創建前端知識體系

博客原文地址: https://finget.github.io/view...

前置內容 瀏覽器主要進程

瀏覽器是多進程的,主要分爲:javascript

  • 瀏覽器主進程:只有一個,主要控制頁面的建立、銷燬、網絡資源管理、下載等。
  • 第三方插件進程:每一種類型的插件對應一個進程,僅當使用該插件時才建立。
  • GPU進程:最多一個,用於3D繪製等。
  • 瀏覽器渲染進程(瀏覽器內核):每一個Tab頁對應一個進程,互不影響。

第一部分 輸入網址並解析

這裏咱們只考慮輸入的是一個URL 結構字符串,若是是非 URL 結構的字符串,則會用瀏覽器默認的搜索引擎搜索該字符串。css

URL的組成

URL 主要由 協議主機端口路徑查詢參數錨點6部分組成!html

解析URL

輸入URL後,瀏覽器會解析出協議、主機、端口、路徑等信息,並構造一個HTTP請求。前端

  1. 瀏覽器發送請求前,根據請求頭的expirescache-control判斷是否命中(包括是否過時)強緩存策略,若是命中,直接從緩存獲取資源,並不會發送請求。若是沒有命中,則進入下一步。
  2. 沒有命中強緩存規則,瀏覽器會發送請求,根據請求頭的last-modifiedetag判斷是否命中協商緩存,若是命中,直接從緩存獲取資源。若是沒有命中,則進入下一步。
  3. 若是前兩步都沒有命中,則直接從服務端獲取資源。

HSTS

因爲安全隱患,會使用 HSTS 強制客戶端使用 HTTPS 訪問頁面。詳見:你所不知道的 HSTS
當你的網站均採用 HTTPS,並符合它的安全規範,就能夠申請加入 HSTS 列表,以後用戶不加 HTTPS 協議再去訪問你的網站,瀏覽器都會定向到 HTTPS。不管匹配到沒有,都要開始 DNS 查詢工做了。java

瀏覽器緩存

強緩存

強制緩存就是向瀏覽器緩存查找該請求結果,並根據該結果的緩存規則來決定是否使用該緩存結果的過程。強緩存又分爲兩種ExpiresCache-Controlnginx

Expires
  • 版本:HTTP/1.0
  • 來源:存在於服務端返回的響應頭中
  • 語法:Expires: Wed, 22 Nov 2019 08:41:00 GMT
  • 缺點:服務器的時間和瀏覽器的時間可能並不一致致使失效
Cache-Control
  • 版本:HTTP/1.1
  • 來源:響應頭和請求頭
  • 語法:Cache-Control:max-age=3600
  • 缺點:時間最終仍是會失效

請求頭: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>

協商緩存

協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程。面試

Last-Modified(響應頭),If-Modified-Since(請求頭)

在瀏覽器第一次給服務器發送請求後,服務器會在響應頭中加上這個字段。
瀏覽器接收到後,若是再次請求,會在請求頭中攜帶If-Modified-Since字段,這個字段的值也就是服務器傳來的最後修改時間。
服務器拿到請求頭中的If-Modified-Since的字段後,其實會和這個服務器中該資源的最後修改時間Last-Modified對比,詢問服務器在該日期後資源是否有更新,有更新的話就會將新的資源發送回來。

可是若是在本地打開緩存文件,就會形成 Last-Modified 被修改,因此在 HTTP / 1.1 出現了 ETag

ETag(響應頭)、If-None-Match(請求頭)

ETag是服務器根據當前文件的內容,給文件生成的惟一標識,只要裏面的內容有改動,這個值就會變。服務器經過響應頭把這個值給瀏覽器。
瀏覽器接收到ETag的值,會在下次請求時,將這個值做爲If-None-Match這個字段的內容,並放到請求頭中,而後發給服務器。

若是兩種方式都支持的話,服務器會優先考慮ETag

存儲位置

  • Service Worker

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

Memory Cache 也就是內存中的緩存,主要包含的是當前中頁面中已經抓取到的資源,例如頁面上已經下載的樣式、腳本、圖片等。讀取內存中的數據確定比磁盤快,內存緩存雖然讀取高效,但是緩存持續性很短,會隨着進程的釋放而釋放。 一旦咱們關閉Tab頁面,內存中的緩存也就被釋放了。

那麼既然內存緩存這麼高效,咱們是否是能讓數據都存放在內存中呢?
這是不可能的。計算機中的內存必定比硬盤容量小得多,操做系統須要精打細算內存的使用,因此能讓咱們使用的內存必然很少。

須要注意的事情是,內存緩存在緩存資源時並不關心返回資源的HTTP緩存頭 Cache-Control是什麼值,同時資源的匹配也並不是僅僅是對URL作匹配,還可能會對 Content-Type,CORS等其餘特徵作校驗。
  • Disk Cache

Disk Cache 也就是存儲在硬盤中的緩存,讀取速度慢點,可是什麼都能存儲到磁盤中,比之 Memory Cache 勝在容量和存儲時效性上。

  • Push Cache

Push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它纔會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,而且緩存時間也很短暫,在Chrome瀏覽器中只有5分鐘左右,同時它也並不是嚴格執行HTTP頭中的緩存指令。

  1. 全部的資源都能被推送,而且可以被緩存,可是 EdgeSafari 瀏覽器支持相對比較差
  2. 能夠推送 no-cacheno-store 的資源
  3. 一旦鏈接被關閉,Push Cache 就被釋放
  4. 多個頁面可使用同一個HTTP/2的鏈接,也就可使用同一個Push Cache。這主要仍是依賴瀏覽器的實現而定,出於對性能的考慮,有的瀏覽器會對相同域名但不一樣的tab標籤使用同一個HTTP鏈接。
  5. Push Cache 中的緩存只能被使用一次
  6. 瀏覽器能夠拒絕接受已經存在的資源推送
  7. 你能夠給其餘域名推送資源

DNS域名解析

在發起http請求以前,瀏覽器首先要作去得到咱們想訪問網頁的IP地址,瀏覽器會發送一個UDP的包給DNS域名解析服務器。

遞歸查詢

咱們的瀏覽器、操做系統、路由器都會緩存一些URL對應的IP地址,統稱爲DNS高速緩存。這是爲了加快DNS解析速度,使得沒必要每次都到根域名服務器中去查詢。

迭代查詢

迭代查詢的方式就是,局部的DNS服務器並不會本身向其餘服務器進行查詢,而是把可以解析該域名的服務器IP地址返回給客戶端,客戶端會不斷的向這些服務器進行查詢,直到查詢到了位置,迭代的話只會幫你找到相關的服務器,而後說我如今比較忙,你本身去找吧。

DNS負載均衡

DNS還有負載均衡的做用,如今不少網站都有多個服務器,當一個網站訪問量過大的時候,若是全部請求都請求在同一個服務器上,可能服務器就會崩掉,這時候就用到了DNS負載均衡技術,當一個網站有多個服務器地址時,在應答DNS查詢的時候,DNS服務器會對每一個查詢返回不一樣的解析結果,也就是返回不一樣的IP地址,從而把訪問引導到不一樣的服務器上去,來達到負載均衡的目的。例如能夠根據每臺機器的負載量,或者該機器距離用戶的地理位置距離等等條件。

第二部分 TCP/IP鏈接:三次握手

網絡協議分層

TCP/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種標示:

  • SYN(synchronous創建聯機)
  • ACK(acknowledgement 確認)
  • PSH(push傳送)
  • FIN(finish結束)
  • RST(reset重置)
  • URG(urgent緊急)

第一次握手:主機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…

ISN是固定的嗎?

當一端爲創建鏈接而發送它的SYN時,它爲鏈接選擇一個初始序號。ISN隨時間而變化,所以每一個鏈接都將具備不一樣的ISN。ISN能夠看做是一個32比特的計數器,每4ms加1 。這樣選擇序號的目的在於防止在網絡中被延遲的分組在之後又被傳送,而致使某個鏈接的一方對它作錯誤的解釋。

三次握手的其中一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number),以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。若是 ISN 是固定的,攻擊者很容易猜出後續的確認號,所以 ISN 是動態生成的。

三次握手過程當中能夠攜帶數據嗎?

其實第三次握手的時候,是能夠攜帶數據的。可是,第一次、第二次握手不能夠攜帶數據。

爲何這樣呢?你們能夠想一個問題,假如第一次握手能夠攜帶數據的話,若是有人要惡意攻擊服務器,那他每次都在第一次握手中的 SYN 報文中放入大量的數據。由於攻擊者根本就不理服務器的接收、發送能力是否正常,而後瘋狂着重複發 SYN 報文的話,這會讓服務器花費不少時間、內存空間來接收這些報文。

也就是說,第一次握手不能夠放數據,其中一個簡單的緣由就是會讓服務器更加容易受到攻擊了。而對於第三次的話,此時客戶端已經處於 ESTABLISHED 狀態。對於客戶端來講,他已經創建起鏈接了,而且也已經知道服務器的接收、發送能力是正常的了,因此能攜帶數據也沒啥毛病。

SYN攻擊?

服務器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,因此服務器容易受到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 攻擊的方法有以下幾種:

  • 縮短超時(SYN Timeout)時間
  • 增長最大半鏈接數
  • 過濾網關防禦
  • SYN cookies技術

第三部分 HTTP請求

HTTP 發展歷史

HTTP/0.9

  • [x] 只有一個命令GET
  • [x] 響應類型: 僅 超文本
  • [x] 沒有header等描述數據的信息
  • [x] 服務器發送完畢,就關閉TCP鏈接

HTTP/1.0

  • [x] 增長了不少命令(post HESD )
  • [x] 增長status codeheader
  • [x] 多字符集支持、多部分發送、權限、緩存等
  • [x] 響應:再也不只限於超文本 (Content-Type 頭部提供了傳輸 HTML 以外文件的能力 — 如腳本、樣式或媒體文件)

HTTP/1.1

  • [x] 持久鏈接。TCP三次握手會在任何鏈接被創建以前發生一次。最終,當發送了全部數據以後,服務器發送一個消息,表示不會再有更多數據向客戶端發送了;則客戶端纔會關閉鏈接(斷開 TCP)
  • [x] 支持的方法: GET , HEAD , POST , PUT ,DELETE , TRACE , OPTIONS
  • [x] 進行了重大的性能優化和特性加強,分塊傳輸、壓縮/解壓、內容緩存磋商、虛擬主機(有單個IP地址的主機具備多個域名)、更快的響應,以及經過增長緩存節省了更多的帶寬

HTTP2

  • [x] 全部數據以二進制傳輸。HTTP1.x是基於文本的,沒法保證健壯性,HTTP2.0絕對使用新的二進制格式,方便且健壯
  • [x] 同一個鏈接裏面發送多個請求再也不須要按照順序來
  • [x] 頭信息壓縮以及推送等提升效率的功能

HTTP3

  • [x] QUIC「快速UDP互聯網鏈接」(Quick UDP Internet Connections)

HTTP3 的主要改進在傳輸層上。傳輸層不會再有我前面提到的那些繁重的 TCP 鏈接了。如今,一切都會走 UDP。

HTTP3詳細介紹

HTTP協議特色

  1. 支持客戶/服務器模式。
  2. 簡單快速客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法經常使用的有 GET、HEAD、POST。每種方法規定了客戶與服務器聯繫的類型不一樣。因爲 HTTP協議簡單,使得HTTP服務器的程序規模小,於是通訊速度很快。
  3. 靈活:HTTP容許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type(Content-Type是HTTP包中用來表示內容類型的標識)加以標記。
  4. 無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。
  5. 無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。
如今 HTTP3 最快!

HTTP報文

請求報文:

響應報文:

各協議與HTTP協議關係

  • DNS 服務:解析域名至對應的IP地址
  • HTTP 協議:生成針對目標Web服務器的HTTP請求報文
  • TCP 協議:將請求報文按序號分割成多個報文段
  • IP 協議:搜索對方的地址,一邊中轉一邊傳送
  • TCP 協議:按序號以原來的順序重組請求報文請求的處理結果也一樣利用TCP/IP協議向用戶進行回傳
  • TCP是底層通信協議,定義的是數據傳輸和鏈接方式的規範;
  • HTTP是應用層協議,定義的是傳輸數據的內容的規範;
  • HTTP協議中的數據是利用TCP協議傳輸的,因此支持HTTP也就必定支持TCP。

關於HTTP的東西還有不少,我在最後放了張 大圖

HTTPS

在HTTP的基礎上再加一層TLS(傳輸層安全性協議)或者SSL(安全套接層),就構成了HTTPS協議。

HTTPS 默認工做在 TCP 協議443端口,它的工做流程通常如如下方式:

  1. TCP 三次同步握手
  2. 客戶端驗證服務器數字證書
  3. DH 算法協商對稱加密算法的密鑰、hash 算法的密鑰
  4. SSL 安全加密隧道協商完成
  5. 網頁以加密的方式傳輸,用協商的對稱加密算法和密鑰加密,保證數據機密性;用協商的hash算法進行數據完整性保護,保證數據不被篡改。

  1. 客戶端向服務端發送 Client Hello 消息,其中攜帶客戶端支持的協議版本、加密算法、壓縮算法以及客戶端生成的隨機數;
  2. 服務端收到客戶端支持的協議版本、加密算法等信息後;

    1. 向客戶端發送 Server Hello 消息,並攜帶選擇特定的協議版本、加密方法、會話 ID 以及服務端生成的隨機數;
    2. 向客戶端發送 Certificate 消息,即服務端的證書鏈,其中包含證書支持的域名、發行方和有效期等信息;
    3. 向客戶端發送 Server Key Exchange 消息,傳遞公鑰以及簽名等信息;
    4. 向客戶端發送可選的消息 Certificate Request,驗證客戶端的證書;
    5. 向客戶端發送 Server Hello Done 消息,通知服務端已經發送了所有的相關信息;
  3. 客戶端收到服務端的協議版本、加密方法、會話 ID 以及證書等信息後,驗證服務端的證書;

    1. 向服務端發送 Client Key Exchange 消息,包含使用服務端公鑰加密後的隨機字符串,即預主密鑰(Pre Master Secret);
    2. 向服務端發送 Change Cipher Spec 消息,通知服務端後面的數據段會加密傳輸;
    3. 向服務端發送 Finished 消息,其中包含加密後的握手信息;
  4. 服務端收到 Change Cipher SpecFinished 消息後;

    1. 向客戶端發送 Change Cipher Spec 消息,通知客戶端後面的數據段會加密傳輸;
    2. 向客戶端發送 Finished 消息,驗證客戶端的 Finished 消息並完成 TLS 握手;

TLS 握手的關鍵在於利用通訊雙發生成的隨機字符串和服務端的證書公鑰生成一個雙方通過協商後的對稱密鑰,這樣通訊雙方就可使用這個對稱密鑰在後續的數據傳輸中加密消息數據,防止中間人的監聽和攻擊,保證通信安全。

HTTPS鏈接 須要7次握手,3次TCP + 4次TSL。

第四部分 服務器處理請求並返回 HTTP 報文

每臺服務器上都會安裝處理請求的應用——Web Server。常見的Web Server 產品有 apachenginxIISLighttpd 等。

HTTP請求通常能夠分爲兩類,靜態資源 和 動態資源。

請求訪問靜態資源,這個就直接根據url地址去服務器裏找就行了。

請求動態資源的話,就須要web server把不一樣請求,委託給服務器上處理相應請求的程序進行處理(例如 CGI 腳本,JSP 腳本,servlets,ASP 腳本,服務器端 JavaScript,或者一些其它的服務器端技術等),而後返回後臺程序處理產生的結果做爲響應,發送到客戶端。

服務器在處理請求的時候主要有三種方式:

  • 第一種:是用一個線程來處理全部的請求,而且同時只能處理一個請求,可是這樣的話性能是很是的低的。
  • 第二種:是每個請求都給他分配一個線程可是當連接和請求比較多的時候就會致使服務器的cpu不堪重負。
  • 第三種:就是採用複用I/O的方式來處理例如經過epoll方式監視全部連接當連接狀態發生改變的時候纔去分配空間進行處理。

第五部分 瀏覽器渲染頁面

DOM樹

字節 → 字符 → 令牌 → 節點 → 對象模型。

<!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>

  • 轉換: 瀏覽器從磁盤或網絡讀取 HTML 的原始字節,並根據文件的指定編碼(例如 UTF-8)將它們轉換成各個字符。
  • 令牌化: 瀏覽器將字符串轉換成 W3C HTML5 標準規定的各類令牌,例如,「<html>」、「<body>」,以及其餘尖括號內的字符串。每一個令牌都具備特殊含義和一組規則。
  • 詞法分析: 發出的令牌轉換成定義其屬性和規則的「對象」。
  • DOM 構建: 最後,因爲 HTML 標記定義不一樣標記之間的關係(一些標記包含在其餘標記內),建立的對象連接在一個樹數據結構內,此結構也會捕獲原始標記中定義的父項-子項關係: HTML 對象是 body 對象的父項,body 是 paragraph 對象的父項,依此類推。

CSS 對象模型 (CSSOM)

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }

佈局樹Layout Tree

  • DOM 樹與 CSSOM 樹合併後造成渲染樹。
  • 渲染樹只包含渲染網頁所需的節點。
  • 佈局計算每一個對象的精確位置和大小。
  • 最後一步是繪製,使用最終渲染樹將像素渲染到屏幕上。

渲染

渲染流程:

  1. 獲取DOM後分割爲多個圖層
  2. 對每一個圖層的節點計算樣式結果 (Recalculate style--樣式重計算)
  3. 爲每一個節點生成圖形和位置 (Layout--重排,迴流)
  4. 將每一個節點繪製填充到圖層位圖中 (Paint--重繪)
  5. 圖層做爲紋理上傳至GPU
  6. 組合多個圖層到頁面上生成最終屏幕圖像 (Composite Layers--圖層重組)

建立圖層

<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中部分或所有元素的尺寸、結構、或某些屬性發生改變時,瀏覽器從新渲染部分或所有文檔的過程稱爲迴流。

迴流必將引發重繪,而重繪不必定會引發迴流。

引發迴流:

  1. 頁面首次渲染
  2. 瀏覽器窗口大小發生改變
  3. 元素尺寸或位置發生改變
  4. 元素內容變化(文字數量或圖片大小等等)
  5. 元素字體大小變化
  6. 添加或者刪除可見的DOM元素
  7. 激活CSS僞類(例如::hover)
  8. 查詢某些屬性或調用某些方法

引發迴流的屬性和方法:

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • scrollIntoView()、scrollIntoViewIffNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()
如何減小回流
  • css
  1. 避免使用table佈局;
  2. 儘量在DOM樹的最末端改變class;
  3. 避免設置多層內聯樣式;
  4. 將動畫效果應用到position屬性爲absolute或fixed的元素上;
  5. 避免使用CSS表達式(例如:calc())。
  • JS
  1. 避免頻繁操做樣式,最好一次性重寫style屬性,或者將樣式列表定義爲class並一次性更改class屬性。
  2. 避免頻繁操做DOM,建立一個documentFragment,在它上面應用全部DOM操做,最後再把它添加到文檔中。
  3. 也能夠先爲元素設置display: none,操做結束後再把它顯示出來。由於在display屬性爲none的元素上進行的DOM操做不會引起迴流和重繪。
  4. 避免頻繁讀取會引起迴流/重繪的屬性,若是確實須要屢次使用,就用一個變量緩存起來。
  5. 對具備複雜動畫的元素使用絕對定位,使它脫離文檔流,不然會引發父元素及後續元素頻繁迴流。

第六部分 斷開鏈接:TCP 四次分手

  1. 剛開始雙方都處於established狀態,假如是客戶端先發起關閉請求
  2. 第一次揮手:客戶端發送一個FIN報文,報文中會指定一個序列號。此時客戶端處於FIN_WAIT1狀態
  3. 第二次揮手:服務端收到FIN以後,會發送ACK報文,且把客戶端的序列號值+1做爲ACK報文的序列號值,代表已經收到客戶端的報文了,此時服務端處於CLOSE_WAIT狀態
  4. 第三次揮手:若是服務端也想斷開鏈接了,和客戶端的第一次揮手同樣,發送FIN報文,且指定一個序列號。此時服務端處於LAST_ACK的狀態
  5. 須要過一陣子以確保服務端收到本身的ACK報文以後纔會進入CLOSED狀態,服務端收到ACK報文以後,就處於關閉鏈接了,處於CLOSED狀態。

揮手爲何須要四次?

由於當服務端收到客戶端的SYN鏈接請求報文後,能夠直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。可是關閉鏈接時,當服務端收到FIN報文時,極可能並不會當即關閉SOCKET,因此只能先回復一個ACK報文,告訴客戶端,「你發的FIN報文我收到了」。只有等到我服務端全部的報文都發送完了,我才能發送FIN報文,所以不能一塊兒發送。故須要四次揮手。

爲何客戶端發送ACK以後不直接關閉,而是要等一陣子才關閉?

客戶端收到服務端的鏈接釋放報文段後,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入TIME_WAIT(時間等待)狀態。此時TCP未釋放掉,須要通過時間等待計時器設置的時間2MSL後,客戶端才進入CLOSED狀態。若是不等待,客戶端直接跑路,當服務端還有不少數據包要給客戶端發,且還在路上的時候,若客戶端的端口此時恰好被新的應用佔用,那麼就接收到了無用數據包,形成數據包混亂。

爲何TIME_WAIT狀態須要通過2MSL(最大報文生存時間)才能返回到CLOSE狀態?

理論上,四個報文都發送完畢,就能夠直接進入CLOSE狀態了,可是可能網絡是不可靠的,有可能最後一個ACK丟失。因此TIME_WAIT狀態就是用來重發可能丟失的ACK報文。
1 個 MSL 確保四次揮手中主動關閉方最後的 ACK 報文最終能達到對端;
1 個 MSL 確保對端沒有收到 ACK 重傳的 FIN 報文能夠到達。

關於HTTP

若是想要高清大圖或者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
瀏覽器工做原理與實踐

相關文章
相關標籤/搜索