1. 前言
在瀏覽內(nèi)核加載網(wǎng)絡(luò)資源的過程中我們離不開 HTTP 協(xié)議。它是在 Web 上進(jìn)行數(shù)據(jù)交換的基礎(chǔ),同時(shí)也是一種無狀態(tài)的 client-server 協(xié)議。這種無狀態(tài)的屬性促使許多端存儲(chǔ)技術(shù)產(chǎn)生,其中最重要的技術(shù)之一就是** cookie 存儲(chǔ)技術(shù)**,它能方便的將數(shù)據(jù)存儲(chǔ)于客戶端,且在每次請(qǐng)求中都會(huì)在請(qǐng)求頭中攜帶 cookie 數(shù)據(jù)并發(fā)送給 server。
cookie 技術(shù)的便捷性使得它在多種場景中被廣泛使用,有時(shí)候甚至存在濫用情況,對(duì)同一 cookie 實(shí)例,前端、客戶端、服務(wù)端都可以輕易的進(jìn)行增刪改查,我們?cè)谙硎芷浔憬菪缘耐瑫r(shí),也有必要確保其被正確、可控的使用。本文將在前系列文章的基礎(chǔ)上,繼續(xù)深入 WKWebView 源碼,聊聊 cookie 管理那些事,希望給大家?guī)硪恍┬碌囊暯呛驼J(rèn)知,揭開 cookie 管理的迷霧。
2. Cookie 概述
MDN官網(wǎng)(https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)對(duì)cookie的介紹如下:
HTTP cookie(也叫 Web cookie 或?yàn)g覽器 cookie)是保存在瀏覽器本地的一小塊數(shù)據(jù),它會(huì)在瀏覽器向服務(wù)器發(fā)起請(qǐng)求時(shí)被攜帶并發(fā)送到服務(wù)器上。通常,它用于告知服務(wù)端兩個(gè)請(qǐng)求是否來自同一瀏覽器,如保持用戶的登錄狀態(tài)。cookie 使基于無狀態(tài)的 HTTP 協(xié)議記錄穩(wěn)定的狀態(tài)信息成為了可能。
cookie 主要用于以下三個(gè)方面:
會(huì)話狀態(tài)管理:如用戶登錄狀態(tài)、購物車、游戲分?jǐn)?shù)或其它需要記錄的信息。
個(gè)性化設(shè)置:如用戶自定義設(shè)置、主題等。
瀏覽器行為跟蹤:如跟蹤分析用戶行為等。
簡單介紹完 cookie的概念后,接下來我們?cè)俜謩e從前端、后端、客戶端的視角聊聊 cookie 的基本使用。
3. Cookie 基本使用
丨3.1 前端通過 js 操作 cookie
詳細(xì) cookie 格式語法參考 MDN 語法鏈接:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie
// 讀取所有可從當(dāng)前頁面訪問的 cookie
丨3.2 后端配置 cookie
詳細(xì) cookie 格式語法參考 MDN 語法鏈接:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
在 response header 中返回需要種到端上的 cookie ,我們通過 Charles 工具抓包可以看到 header 中如下信息:
丨3.3 客戶端操作 cookie
iOS 系統(tǒng)在 WKHTTPCookieStorage 類中提供如下 API 進(jìn)行 cookie 操作:
@interface WKHTTPCookieStore : NSObject
可以看到,不同場景下的 cookie 操作都是極其簡單的,我們似乎已經(jīng)通過簡單的封裝接口掌握了 cookie 技術(shù),那么問題來了:
(1)cookie 究竟是存儲(chǔ)在哪的?內(nèi)存,還是磁盤?
(2)三種不同場景的 cookie 操作是如何協(xié)同工作的?
現(xiàn)在,我們能回答這些問題嗎?如果不能,請(qǐng)繼續(xù)跟隨我深入 WKWebView 源碼,讓代碼告訴我們答案。
4. WebKit Cookie 技術(shù)原理
再次回到源碼探索的道路,現(xiàn)在我們?cè)倩仡櫼幌略?a target="_blank">《深入理解 WKWebView(入門篇)—— WebKit 源碼調(diào)試與分析》提及的源碼探索的核心技巧:緊緊圍繞 UIProcess、WebContent、NetworkProcess 三大進(jìn)程進(jìn)行理解。
丨4.1 三大進(jìn)程與三種場景
如上圖所示,我們將 cookie 操作的三種場景與三大進(jìn)程進(jìn)行關(guān)聯(lián),其中,
(1)客戶端操作在 UIProcess 進(jìn)程(即我們的 app 進(jìn)程),通過封裝的 WKHTTPCookieStorage 進(jìn)行操作。
(2)前端 js 函數(shù),通過 JSCore 解析執(zhí)行后最終調(diào)用了 WebContent 進(jìn)程中的 C++ 函數(shù)進(jìn)行操作,如下所示:
virtual String cookies(Document&, const URL&) const;
(3)WKWebView 中的網(wǎng)絡(luò)請(qǐng)求最終都是通過 NetworkProcess 中的 NSURLSession 管理的,服務(wù)端網(wǎng)絡(luò)響應(yīng)的cookie 設(shè)置操作都在該進(jìn)程中完成。
丨4.2 三種場景下的協(xié)同工作
cookie 管理協(xié)同圖
如圖所示,描述了三大場景下 cookie 的協(xié)同管理,接下來,我們將結(jié)合該圖解答第二小節(jié)中提出的問題。
問題一:cookie 究竟是存儲(chǔ)在哪的?內(nèi)存,還是磁盤?
UIProcess:
UIProcess 進(jìn)程為 app 進(jìn)程(app 進(jìn)程中其實(shí)有 NSHTTPCookieStorage 倉儲(chǔ)進(jìn)行 cookie 管理,但這不是本文的重點(diǎn),因此不展開來講),蘋果系統(tǒng)為開發(fā)者提供了 WKHTTPCookieStorage API 進(jìn)行 WebKit 內(nèi)核的 cookie 管理,WKHTTPCookieStorage 其實(shí)并不提供實(shí)際的存儲(chǔ)能力,而是封裝了一系列基于進(jìn)程間通信的方法,將 UIProcess 進(jìn)程中發(fā)生的 cookie 操作,發(fā)送到 NetworkProcess 進(jìn)程中進(jìn)行處理,并將執(zhí)行結(jié)果通過回調(diào)函數(shù)返回。
WebContent:
WebContent 進(jìn)程是前端操作 cookie 的進(jìn)程,原則上,每一個(gè)網(wǎng)頁頁面都只能操作當(dāng)前頁面域名下的cookie。因此基于性能考慮,每一個(gè) WebContent 進(jìn)程中會(huì)有一個(gè) cookieCache 實(shí)例,它是 NetworkProcess 進(jìn)程中存儲(chǔ) cookie 的子集,僅存儲(chǔ)當(dāng)前頁面域名下的 cookie,因此 cookieCache 采取了內(nèi)存緩存的方式,其特征是存儲(chǔ)量小,查找速度快。
NetworkProcess:
NSHTTPCookieStorage setCookie 流程圖
NetworkProcess 進(jìn)程是 cookie 存儲(chǔ)的最核心****進(jìn)程,它管理來自網(wǎng)絡(luò)中服務(wù)端 response 中配置的 cookie,同時(shí)也接受來自前端和客戶端的 cookie 操作,是最全的 cookie 存儲(chǔ)中心。通過源碼分析,我們發(fā)現(xiàn)其內(nèi)部還是通過NSHTTPCookieStorage 進(jìn)行管理的, NSHTTPCookieStorage 有如下存儲(chǔ)規(guī)則:
(1)allCookies:所有 cookie 都會(huì)存入字典 allCookies 中,方便快速查詢。當(dāng)我們殺死 app 后,位于內(nèi)存中的 allCookies 字典也會(huì)一同清理掉。
(2)sessionOnly false cookie:對(duì)于某個(gè) cookie,如果其屬性中 sessionOnly 為 false,且設(shè)置的過期時(shí)間未到達(dá),那我們判斷該 cookie 是否具備持久性的邏輯如下:
let persistable = self.allCookies.filter { (_, value) in
(3)持久性 cookie:具備持久性的 cookie 需要存儲(chǔ)到磁盤文件中。存入路徑規(guī)則如下:
let bundlePath = Bundle.main.bundlePath
問題二:三種不同場景的 cookie 操作是如何協(xié)同工作的?
如 cookie 管理協(xié)同圖 所示,不同場景下的 cookie 協(xié)同操作其本質(zhì)就是三大進(jìn)程間的通信:
(1)UIProcess 進(jìn)程并沒有直接管理 cookie,而是通過進(jìn)程間通信的方式,在 NetworkProcess 進(jìn)程中管理 cookie。
(2)所有 WebContent 進(jìn)程都會(huì)注冊(cè)監(jiān)聽 NetWorkProcess 中的 cookie 變更,及時(shí)進(jìn)行相關(guān)變更的同步。
(3)前端 setCookie 操作會(huì)將 cookie 字符串解析為 NSHTTPCookie 實(shí)例,然后將該 cookie 存入 cookieCache 中,并同步到 NetworkProcess 中進(jìn)行存儲(chǔ)。前端執(zhí)行 getCookie 操作會(huì)讀取當(dāng)前頁面域名下的所有 cookie,若判斷 cookieCache 中沒有當(dāng)前頁面域名下的 cookie,考慮到異常情況,會(huì)兜底向 NetworkProcess 發(fā)送請(qǐng)求進(jìn)行 cookie查找。
(4)冷啟動(dòng)時(shí),NetworkProcess 會(huì)初始化 NSHTTPCookieStorage ,并會(huì)將磁盤中的 cookie 讀取出來,設(shè)置到內(nèi)存字典 allCookies 中,同時(shí)將 allCookies 中的 cookie 變更通過廣播的方式告知 WebContent 進(jìn)程,發(fā)生了 cookie變更,需要進(jìn)行 cookie 同步。
(5)來自客戶端的 cookie 操作或者來自服務(wù)端的 cookie 設(shè)置,導(dǎo)致了 NetworkProcess 中的 cookie 變更,都會(huì)通過廣播的方式告知所有 WebContent 進(jìn)程同時(shí)進(jìn)行變更操作。
5. 總結(jié)
總而言之,cookie 操作簡單,使用方便,多端同學(xué)都經(jīng)常與其打交道。理清 WebKit 內(nèi)部的 cookie 管理方式讓我們?cè)诶碚搶用娓私?cookie 的技術(shù)原理。希望閱讀此文后,相關(guān)開發(fā)同學(xué)在日常工作中,如果與 cookie 打了交道,一定要考慮清楚修改帶來的影響面,謹(jǐn)慎操作。
NSHTTPCookieStorage 實(shí)現(xiàn)
NSHTTPCookieStorage 對(duì)應(yīng)的 swift 版本開源代碼如下, 里面有許多基礎(chǔ)類庫的設(shè)計(jì)思路,個(gè)人認(rèn)為非常有參考價(jià)值,有興趣的同學(xué)可以去研究相關(guān)實(shí)現(xiàn):
補(bǔ)充:跨域請(qǐng)求攜帶cookie
基于安全考慮,iOS14系統(tǒng)禁止了跨域請(qǐng)求攜帶cookie(https://webkit.org/tracking-prevention/)。
敬請(qǐng)期待:
深入理解 WKWebView(基礎(chǔ)篇)-- 探究 WebKit 網(wǎng)絡(luò)資源緩存
參考鏈接:
WebKit 源碼:https://github.com/WebKit/WebKit
WebKit 官網(wǎng):https://webkit.org/
Apple 源碼:https://github.com/apple
MDN官網(wǎng):https://developer.mozilla.org/zh-CN