JavaScript 工作原理之十二-網路層探祕及如何提高其效能和安全性

tristan發表於2019-02-27

原文請查閱這裡,略有改動,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland

本系列持續更新中,Github 地址請查閱這裡

這是 JavaScript 工作原理的第十二章。

正如在之前關於渲染引擎的文章中所講的那樣,我們相信好的和偉大的 JavaScript 開發者之間的差別在於後者不僅僅只是理解了語言的具體細節還了解其內部構造和執行環境。

網路簡史

49 年前,ARPAnet 誕生了。它是早期的報文分組交換網路及第一個實現 TCP/IP 協議套件的網路。該網路連通了加利福亞大堂和史丹佛研究所。20 年後,Tim Berners-Lee 分發了一個後來為人所熟知的全球資訊網的 『Mesh』草案。在 49 年的時間裡,網路走過了一段漫長的旅程,從僅僅只是是兩臺電腦間交換資料包文到至少 7500 萬臺伺服器,38 億人使用網際網路以及 13 億個網站。

JavaScript 工作原理之十二-網路層探祕及如何提高其效能和安全性

本文將試著分析現代瀏覽器使用哪些技術來自動提升應用效能(有些你甚至不瞭解),然後著重介紹瀏覽器網路層。最後,提供一些讓瀏覽器提升網路應用程式效能的技巧。

概述

現代瀏覽器專門為快速,高效和安全資料傳輸的網路應用/網站而設計開發的。擁有數以百計的元件執行於各個不同的層級,從程式管理和安全沙箱到 GPU 管線,音訊和視訊及其它更多等等,網路瀏覽器更類似於一個作業系統而不僅僅只是一個軟體。

瀏覽器的整體效能是由一些大型的元件所決定的,這些元件包括:解析,佈局,樣式計算,JavaScript 和 WebAssembly 執行,渲染,當然還有網路堆疊。

一般情況下,工程師們會把網路堆疊看成是一個效能瓶頸。經常會發生這樣的情況因為從網路抓取所有的資源會堵塞渲染剩下的步驟。為了更加高效的網路層,它需要不僅僅只是扮演套接字管理員的角色。在我們看來獲取資源是一個非常簡單的機制,但是實際上它整合自身的優化準則,介面和服務的一整套平臺。

JavaScript 工作原理之十二-網路層探祕及如何提高其效能和安全性

網頁開發者不需要擔心單獨的 TCP 或者 UDP 資料包,請求格式化,快取以及其它正在發生的一切。瀏覽器會處理這些複雜的玩意,這樣就可以專注開發自己的程式。但是,知道其內部的原理可以幫助開發者開發出更加高效和安全的程式。

本質上,當使用者開始和瀏覽器發生互動所產生的情況如下:

  • 使用者在瀏覽器位址列中輸入 URL 地址。
  • 在網路上查詢指定 URL 的資源,瀏覽器開始檢查本地和應用程式快取並試著使用本地副本來響應資源的請求。
  • 當快取不可用,瀏覽器使用 URL 中的域名然後根據域名從 DNS 處獲取伺服器的 IP 地址。如果有域名快取,將不需要進行 DNS 查詢。
  • 瀏覽器建立一個 HTTP 報文表明其請求遠端伺服器的某個網頁。
  • 報文被傳輸到 TCP 層,該層會在 HTTP 報文頭部新增其自身的資訊。該資訊是保持建立的會話的必要資訊。
  • 然後在 IP 層處理報文,該層的主要職責即找出從使用者傳送報文到遠端伺服器的路徑。在 HTTP 報文頭部新增該路徑資訊。
  • 傳輸報文到遠端伺服器。
  • 一旦接收到報文,以類似的方式返回響應資料。

W3C Navigation Timing specification 提供了瀏覽器介面及瀏覽器中每個請求背後的視覺化計時和效能資料。讓我們瀏覽下這些元件,因為每個元件在獲取最佳使用者體驗方面扮演了重要的角色。

JavaScript 工作原理之十二-網路層探祕及如何提高其效能和安全性

整個網路請求過程是相當複雜的並且有許多的層次結構,每一層都有可能成為效能瓶頸。這就是為什麼瀏覽器使用各種技術努力提升其效能,以便把整個網路通訊的效能損耗降至最低。

套接字管理

看些新技術吧:

  • -由應用程式協議,域名和埠號的三個部分組成(比如 https, www.example.com, 443)
  • 套接字池-屬於同源的一組套接字(所有的主流瀏覽器都限制套接字池最多隻能有 6 個套接字)

JavaScript 和 WebAssembly 禁止開發者操作單獨的網路套接字的生命週期,這樣是相當的明智的。這樣不僅僅可以讓你頭髮少掉點而且可以讓瀏覽器自動優化大量的效能,這些效能包括套接字重用,請求優化和延遲繫結,協議協商,強制連線限制及其它的優化措施。

實際上,現代瀏覽器更一步地將請求管理週期從套接字管理中剝離了出來。用套接字池來組織套接字,以源來分組套接字,每個套接字池強制限制其連線數和安全約束。排隊,優先化等待的請求,然後和套接字池中的單個套接字繫結。如果不是伺服器主動關閉這些連線,多個請求可以自動重用相同的套接字。

JavaScript 工作原理之十二-網路層探祕及如何提高其效能和安全性

由於建立一個新的 TCP 連線會帶來額外的效能開銷,重用連線會為其自帶來極大的效能提升。預設情況下,當發起請求的時候,瀏覽器使用所謂的 『keepalive』機制以節省建立到伺服器的新連線所耗費的時間。建立一個新的 TCP 連線的平均時間為:

  • 本地請求-23 毫秒
  • Transcontinental 請求-120 毫秒
  • Intercontinental 請求-225 毫秒

這樣的架構衍生出了一些其它的優化方法。請求可以依據優先順序來以不同的順序執行。瀏覽器可以優化所有套接字間的頻寬分配或者它可以建立套接字以等待預期的請求。

如上所述,這些都是瀏覽器所控制而不用開發者進行干預。但這並不意味著我們無所事事了。選擇正確的資料傳輸所用的網路通訊模式,型別和頻率,正確的協議型別以及正確的伺服器堆疊隧道/優化對於提升整個程式的效能有著至關重要的作用。

一些瀏覽器甚至更進一步。例如,當你使用 Chrome 的時候,當使用者使用的時候它會進行自我學習從而變得更加快速。它基於訪問過的網頁和典型的瀏覽器模式來進行學習,這樣就可以預期可能的使用者行為且在使用者進行任意操作之前進行操作。最簡單的例子即當使用者懸停在某個連結上的時候預渲染頁面。如果你想學習更多關於 Chrome 優化技術的文章,可以檢視 High-Performance Browser Networking 這本書的 www.igvita.com/posa/high-p… 章節。

網路安全和沙箱

允許瀏覽器操作單獨的套接字有另一個非常重要的目的即:瀏覽器就可以針對不被信任的程式資源強制實施一套一致的安全和政策約束措施。例如,瀏覽器禁止通過 API 直接訪問原始網路套接字,因為這樣會導致任意可疑的程式隨意連線任意主機。瀏覽器也強制連線數限制以保護伺服器免受由於客戶端訪問而耗盡其資源。

瀏覽器格式化所有流出的請求以強制格式正確和一致的協議語義來保護伺服器。同樣地,瀏覽器會自動解碼響應內容以保護使用者免受可疑伺服器的攻擊。

TSL 協商

Transport Layer Security (TLS) 是一個為計算機網路提供通訊安全的加密協議。它廣泛應用於大量應用程式,其中之一即瀏覽網頁。網站可以使用 TLS 來保證伺服器和網頁瀏覽器之間的所有通訊安全。

整個 TLS 握手過程包含以下幾個步驟:

  1. 客戶端向伺服器傳送 『Client hello』 資訊,附帶著客戶端隨機值和支援的密碼組合。
  2. 伺服器返回給客戶端 『Server hello』資訊,附帶著伺服器隨機值。
  3. 伺服器返回給客戶端認證證照及或許要求客戶端返回一個類似的證照。伺服器返回『Server hello done』資訊。
  4. 如果伺服器要求客戶端傳送一個證照,客戶端進行傳送。
  5. 客戶端建立一個隨機的 Pre-Master 金鑰然後使用伺服器證照中的公鑰來進行加密,向伺服器傳送加密過的 Pre-Master 金鑰。
  6. 伺服器收到 Pre-Master 金鑰。伺服器和客戶端各自生成基於 Pre-Master 金鑰的主金鑰和會話金鑰。
  7. 客戶端給伺服器傳送一個 『Change cipher spec』的通知,表明客戶端將會開始使用新的會話金鑰來雜湊和加密訊息。客戶端也傳送了一個 『Client finished』的訊息。
  8. 伺服器接收到『Change cipher spec』的通知然後使用會話鑰匙來切換其記錄層安全狀態為對稱加密狀態。伺服器返回客戶端一個 『Server finished』訊息。
  9. 客戶端和伺服器現在可以通過建立的安全通道來交換程式資料。所有客戶端和伺服器之間傳送的資訊都會使用會話金鑰進行加密。

每當發生任何驗證失敗的時候,使用者會收到警告。比如伺服器使用自簽名的證照。

同源策略

當兩個頁面的協議,埠(如果有指定)以及主機名都是一樣的則稱為同源。

以下為一些可能包含跨域的資源示例:

  • <script src=”…”></script> 裡面的 JavaScript 程式碼。語法錯誤的錯誤資訊僅適用於同源指令碼。
  • <link rel=”stylesheet” href=”…”> 的 CSS。由於 CSS 的鬆散語法規則,跨域 CSS 要求正確的 Content-Type 頭。各個瀏覽器的限制不同。
  • <img> 圖片
  • <video><audio> 媒體檔案
  • <object><embed> 和 <applet> 外掛
  • @font-face 字型。一些瀏覽器允許跨域字型,其它則要求同源字型。
  • 和 相關的一切內容。網站可以使用  X-Frame-Options 頭來防止此種跨域互動。

    以上的列表還遠遠不夠;該列表旨在強調工作中的『最小特權』原則。瀏覽器只為應用程式程式碼暴露出其所必需的介面和資源:應用提供資料和 URL 地址,然後瀏覽器格式化請求及處理每條連線的整個生命週期。

    需要注意的是並沒有一個簡單的 『同源策略』概念。

    相反,有一系列相關的機制來強制限制瀏覽器的 DOM 訪問,cookie 和 會話狀態管理,網路連線和其它元件。

    資源和客戶端狀態快取

    最好和最快的請求即不建立請求。在分派一個請求前,瀏覽器自動檢查其資源快取,進行必要的驗證檢查然後當指匹配指定的條件時返回一份本地資源拷貝。如果快取中沒有可用的本地資源,則發起網路請求然後把響應內容自動放置於快取中以備之後的訪問(如果這是被允許的)。

    • 瀏覽器自動為每個資源求值快取指令。
    • 當條件允許時,瀏覽器自動重新恢復過期資源
    • 瀏覽器自動處理快取和資源回收的大小

    管理一個高效和優化的資源快取是非常困難的。謝天謝地,瀏覽器為我們處理了整個複雜的玩意,而我們所需要做的即保證伺服器返回恰當的快取指令;想了解更多可以看 客戶端資源快取 文章。你為網頁上的所有資源新增 Cache-Control,ETag,和 Last-Modified 的響應頭資訊。

    最後,一個經常被忽略但至關重要的瀏覽器功能即其提供了驗證,會話和 cookie 管理。瀏覽器為每個源維護單獨的『cookie jars』,通過提供必要的程式和伺服器介面來讀寫新的 cookie,會話和認證資料,以及自動掛載和處理適當的 HTTP 頭來為我們自動處理整個過程。

    例子:

    一個簡單但明瞭的用來展示瀏覽器的延遲會話狀態管理的方便性的例子即:多個選項卡或者瀏覽器視窗可以共享一個認證會話,反之亦然;一個選項卡中的登出操作可以使所有其它開啟視窗的會話失效。

    應用程式介面和協議

    瞭解了網路服務之後,最終要講到應用程式介面和協議。眾所周知,更底層的結構提供了一組廣泛的重要服務:套接字和連線管理,請求和響應處理,各種安全策略,快取及其它更多的強制措施。每當初始化一個 HTTP 請求或者 XMLHttpRequest,一個持久的服務推事件或者 WebSocket 會話抑或開啟一個 WebRTC 連線,我們就是在和部分或者所有這些底層服務進行互動。

    沒有單一的最佳協議或者介面。每個複雜的程式都會基於不同的要求混合使用不同的傳輸協議:和瀏覽器快取的互動,協議開銷,訊息延遲,可靠性,資料傳輸型別以及其它。一些協議擁有低資料傳輸延遲的特性(比如伺服器推事件,WebSocket),但是可能不符合其它重要的場合,比如利用瀏覽器快取或者支援任意情況下的二進位制資料傳輸的能力。

    一些可以用來提升程式效能和安全的小技巧

    • 一直在請求中使用 『Connection: Keep-Alive』頭資訊。瀏覽器預設在請求頭中新增 『Connection: Keep-Alive』。保證伺服器也使用同樣的機制。
    • 使用合適的 Cache-Control,Etag 和 Last-Modified 頭資訊以便節省瀏覽器的下載時間。
    • 花些時間調整和優化伺服器。這是奧祕所在!注意這一過程是否針對每個程式和所傳輸的資料。
    • 一直使用 TLS!特別是如果程式中包含有任意型別的認證。
    • 研究瀏覽器所提供的安全策略並且在程式中強制實施。

    擴充套件

    關於字型檔案的跨域問題可以檢視這裡這裡

    打個廣告 ^.^

    今日頭條招人啦!傳送簡歷到 likun.liyuk@bytedance.com ,即可走快速內推通道,長期有效!國際化PGC部門的JD如下:c.xiumi.us/board/v5/2H…,也可內推其他部門!

    本系列持續更新中,Github 地址請查閱這裡

相關文章