zipoutputstream压缩文件响应到浏览器_现代浏览器内部机制之导航这件小事
讓我們以一個常見的例子作為起點:輸入一個 url,瀏覽器會從服務端獲取數據并將頁面展示出來。本文會聚焦在用戶通過瀏覽器向一個站點發起訪問請求以及瀏覽器準備渲染這個頁面的部分,這個過程我稱之為導航。
以一個瀏覽器進程為起點
我們在上一篇文章中提過,所有處于窗口之外的部分都由同一個瀏覽器進程進行掌管。瀏覽器的進程又同時擁有許多線程,掌管瀏覽器的不同部分:UI 線程用來繪制頂部的操作按鈕和輸入框、網絡線程負責處理并接收來自互聯網的數據、存儲線程控制著訪問本地文件的權限等。當你將一個網站的 url 輸入到瀏覽器的地址欄時,此刻正是瀏覽器進程中的 UI 線程在起作用。
一次簡單的導航
Step 1:處理用戶輸入
當用戶開始在地址欄輸入時,UI 線程首先會問:“大兄弟,你輸入的是個查詢字符串還是網站地址?”。因為 Chrome 的地址欄同時還是個搜索框,所以 UI 線程需要解析用戶的輸入,才能決定該直接訪問網址還是把用戶的輸入丟給搜索引擎處理。
Step 2:開始導航
當用戶按下回車鍵后,UI 線程要求網絡線程去獲取網站的內容。窗口的 Tab 上會開始轉菊花,網絡線程會采用一系列的協議和操作(比如 DNS)查詢必要的信息并為請求建立連接。
此時,網絡線程可能會收到來自服務器的一個標記著重定向指令的頭部比如 HTTP 301,在這種情況下,網絡線程會把這件事情告訴 UI 線程,之后則會發起一次指向重定向地址的新的網絡請求。
Step 3:讀取響應
當響應的數據開始傳送到瀏覽器時,網絡線程會在必要的情況下檢查一些來自響應的字段。響應數據的 Content-Type 字段會表示當前返回的是哪種類型的數據,但它也不完全靠譜,經常會出現丟失或者干脆不準確的情況,但也不用擔心,MIME 嗅探[3]會完成缺失的工作。正如源碼[4]的注釋中寫道,這是一個可以被解釋為 hack 的方案,如果感興趣的話,你也可以去閱讀這些注釋,這樣就能了解不同的瀏覽器是如何將實際的數據與 Content-Type 匹配了。
如果響應數據是一個 HTML 文件,那么接下來的一步會是把數據傳遞給瀏覽器的渲染進程;但如果數據是 zip 壓縮文件或其他類型的文件,意味著這將被定位成一次下載動作,于是瀏覽器會將數據轉交給下載管理器去處理。
通常這一步也是安全檢測[5]發生的時候:如果域名或響應數據和已知的惡意網站匹配時,網絡進程會拋出一個警告,并展現一個告警的頁面。另外,CORB[6] 檢測也會開始工作,確保那些來自敏感站點的跨站響應數據不會進入到瀏覽器的渲染進程中。
Step 4:渲染進程
網絡線程以獲取了全部的數據,并完成了所有需要的檢查,此刻它自信的告訴 UI 線程:“小兄弟,數據準備好了!”。接著,UI 線程會喚起一個渲染進程去渲染頁面。
由于網絡情況的不可控,一個請求可能會花上好幾百毫秒才能把響應數據拿回來,所以這里瀏覽器默認開啟了用來加速這一過程的優化。在 Step 2 中,當 UI 線程將需要請求的 url 告訴網絡線程時,其實它本身已經知道要導航到哪個網站了,于是 UI 線程在把 url 傳遞給網絡線程的同時,會嘗試啟動一個渲染進程。如果一切都按照預期正常進行的話,當網絡線程拿到數據時,渲染進程就已經處于待命狀態了。也會有例外的情況:比如導航重定向到一個另外的站點,那么預先啟動好的渲染進程將不會被使用,這導致 UI 線程需要重新啟動一個渲染進程。
Step 5:觸發導航
現在我們假設數據和渲染進程都準備好了,瀏覽器進程通過 IPC 告知渲染進程可以出發本次導航了。與此同時,數據流也將傳遞給渲染進程,這樣后者就能繼續接收 HTML 數據。一旦瀏覽器收到了來自渲染進程的導航啟動信號,這次導航也就完成了,下一步進入文檔的加載階段。
到這會兒,瀏覽器的地址欄更新,安全指示符和站點的設置 UI 會將新頁面的信息呈現出來。當前窗口的 session 將會更新,剛導航到的頁面會被后退/前進按鈕記錄到窗口的頁面歷史中。為了便于在關閉窗口時恢復頁面,歷史的會話記錄會保存在本地的磁盤上。
Extra Step:初始加載完成
當導航觸發后,渲染進程會持續接收資源并渲染頁面。我們將在下一篇文章中討論這一步的更多細節。當渲染進程“完成”渲染后,它會通過 IPC 告知瀏覽器進程(頁面的 onload 事件均已執行完畢后),UI 線程也就不再在 tab 上轉菊花了。
上面的“完成”兩個字,之所以打了雙引號,因為在實際場景中,它通常并不真正意味著完成,因為客戶端的 JavaScript 可能在此時持續地加載資源并渲染新的視圖。
導航到另一個網站
一次簡單的導航截至目前已經完成了。假如這時用戶輸入了一個不同的 url 會發生什么呢?其實也沒啥,瀏覽器進程會按照上面的步驟導航到這個網站。但在這一切開始之前,瀏覽器會檢查當前已經渲染好了的網站是否需要在網頁卸載之前搞一點事情,這就是 beforeunload 事件。
在 beforeunload 事件中,我們可以在用戶即將跳轉至其他頁面或者關閉 Tab 的時候發起一個“確認離開當前頁面?”的二次確認。Tab 中的所有東西都由渲染進程控制著,當然也包括開發者編寫的 JavaScript,所以當一個新的導航請求即將到來時,瀏覽器進程會對當前的渲染進程做最后的檢查。
我們應當盡量避免在 beforeunload 中添加總會執行的事件代碼,這會造成更多的交互延時,畢竟它們總會在新的導航開始之前執行。只在需要的時候添加這些代碼,比如提醒用戶如果進入新的頁面那么當前頁面的數據會丟失。
如果導航是在渲染進程中被創建的(比如用戶點擊了頁面上的某一鏈接或者在 JavaScript 運行了 window.location.href = 'https://kyrieliu.cn' ),則當前的渲染進程會首先檢查是 beforeunload 中是否有東西需要執行。之后,它會經歷與瀏覽器進程直接發起導航后一樣的導航過程。
當新的導航將發往與當前頁面不同的站點時,瀏覽器將會創建一個新的渲染進程去處理這些新工作,舊的渲染進程則則用來在剩余的時間里處理諸如 unload 的頁面事件。如果你想了解更多的話,可以看看頁面生命周期概覽[7]和頁面生命周期 API[8]這兩篇文章。
如果有 Service Worker...
Service Worker[9] 的引入會對頁面的導航流程帶來一些改變。Service Worker 是一種可以在應用代碼中編寫網絡代理的方法;增強了開發者對于本地緩存以及何時發起網絡請求的控制。如果 Service Worker 提前設置了從本地緩存中讀取某一頁面的數據,那么也就不需要發起網絡請求了。
需要明確的一點是,即使 Service Worker 提供了聽起來很高端的功能,但它實質上也是運行在渲染進程中的 JavaScript 代碼。那么問題來了:當用戶發起一次導航時,瀏覽器進程是如何知道目標站點存在一個 Service Worker 的呢?
當一個 Service Worker 注冊后,它的作用域會保存在一個引用中(你可以通過 Service Worker 的生命周期[10] 這篇文章了解我所說的“作用域”)。當導航發生時,網絡線程會依據域名在已注冊的 Service Worker 作用域集合中查詢,如果找到某個對應的 Service Worker,UI 線程會發起一個渲染進程去執行 Service Worker 中的代碼。Service Worker 可以從本地緩存中加載數據(無需發起網絡請求),也可以選擇通過網絡請求獲取最新的資源和數據。
導航預加載
相信你可以發現,如果 Service Worker 最終決定從網絡中請求數據,那么之前在瀏覽器進程和渲染進程之間所發生的通信都將成為導致響應延時的罪魁禍首。導航預加載[11]就是用來加速這一進程的機制:與 Service Worker 并行啟動去加載資源。它將為這些請求設置一個 Header,由服務端來決定為這些請求發送不同的內容;比如,僅返回更新的數據而不是整個文檔。
總結
在這篇文章中,我們檢視了在導航時都發生了什么,以及 Web 應用的代碼比如響應頭和客戶端的 JavaScript 代碼是如何與瀏覽器進行交互的。了解了瀏覽器是如何一步步從網絡中請求數據的,這能讓我們更好的理解很多 API 比如導航預加載的誕生初衷。
在下一篇文章中,我們會深入討論瀏覽器是如何執行 HTML/CSS/JavaScript 代碼從而完成一個頁面的渲染的。
參考資料
[1]Inside Look at Modern Web Browser (part 2): https://developers.google.com/web/updates/2018/09/inside-browser-part2
[2]Mariko Kosaka: https://developers.google.com/web/resources/contributors/kosamari
[3]MIME 嗅探: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
[4]源碼: https://cs.chromium.org/chromium/src/net/base/mime_sniffer.cc?sq=package:chromium&dr=CS&l=5
[5]安全檢測: https://safebrowsing.google.com/
[6]CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
[7]頁面生命周期概覽: https://developers.google.cn/web/updates/2018/07/page-lifecycle-api#overview_of_page_lifecycle_states_and_events
[8]頁面生命周期 API: https://developers.google.cn/web/updates/2018/07/page-lifecycle-api
[9]Service Worker: https://developers.google.cn/web/fundamentals/primers/service-workers
[10]Service Worker 的生命周期: https://developers.google.cn/web/fundamentals/primers/service-workers/lifecycle
[11]導航預加載: https://developers.google.cn/web/updates/2017/02/navigation-preload
點個在看,贊?支持我吧總結
以上是生活随笔為你收集整理的zipoutputstream压缩文件响应到浏览器_现代浏览器内部机制之导航这件小事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql readline_readL
- 下一篇: mysql lbs 计算距离_使用mys