深入理解 WKWebView(基礎(chǔ)篇)-- 探究 WebKit 緩存
作者丨童紅明
來源丨百度App技術(shù)
1. 前言
1. 前言
緩存可以減少冗余的數(shù)據(jù)傳輸,解決網(wǎng)絡(luò)瓶頸問題,降低服務(wù)端壓力,提升頁面加載速度。高效利用緩存可大幅提升頁面加載速度,提升用戶的瀏覽體驗(yàn)。WKWebView 使用緩存技術(shù)存儲前后端資源,用戶提高頁面性能和用戶體驗(yàn)。因?yàn)?WKWebView 的封閉性,我們無法針對原生 WKWebView 做較深度化的定制,但對于 WebKit 緩存源碼的探究,將幫助我們更好的使用和理解緩存。本文將延續(xù) 《iOS 端 webkit 源碼調(diào)試與分析》的思路,結(jié)合源碼枚舉 WKWebView 中的各類緩存,并重點(diǎn)講述其中的 HTTP 協(xié)議緩存,幫助讀者更好的理解 WebKit 中緩存的設(shè)計(jì)思路。
2. 緩存簡介
丨2.1 緩存類型
2.1.1 WebKit 標(biāo)準(zhǔn)緩存類型
// HTTP 磁盤緩存。WKWebsiteDataTypeDiskCache,// html離線Web應(yīng)用程序緩存。WKWebsiteDataTypeOfflineWebApplicationCache,// HTTP 內(nèi)存緩存。WKWebsiteDataTypeMemoryCache,// 會話存儲:存儲對數(shù)據(jù)只有在同一個會話中的頁面才能訪問并且當(dāng)會話結(jié)束后數(shù)據(jù)也隨之銷毀。// 因此sessionStorage不是一種持久化的本地存儲,僅僅是會話級別的存儲WKWebsiteDataTypeSessionStorage,// 本地存儲:localStorage 類似 sessionStorage,但其區(qū)別在于,存儲在 localStorage 的數(shù)據(jù)可以長期保留.WKWebsiteDataTypeLocalStorage,// Cookies存儲:存儲所有的cookie數(shù)據(jù)WKWebsiteDataTypeCookies,// IndexedDB數(shù)據(jù)庫:IndexedDB是WebSQL數(shù)據(jù)庫的取代品。IndexedDB是key-value型數(shù)據(jù)庫,操作簡單。WKWebsiteDataTypeIndexedDBDatabases,// webSQL數(shù)據(jù)庫:W3C組織在2010年11月18日廢棄了webSql 數(shù)據(jù)庫,該數(shù)據(jù)庫接口操組復(fù)雜,對用戶不友好。WKWebsiteDataTypeWebSQLDatabases
通過數(shù)據(jù)分析,主要是 indexedDB 與 NetworkCache 占據(jù)較大比例,可達(dá)80%以上。WebKit 磁盤緩存分布如下表:
| 磁盤文件目錄 | 緩存類型 |
| Library/WebKit | IndexedDB LocalStorage MediaKeys ResourceLoadStatistics |
| Library/Caches/WebKit | CacheStorage NetworkCache offlineWebApplicationCache ServiceWorkers |
2.1.2 前進(jìn)后退緩存 – pageCache
在 WebKit 中,pageCache 其實(shí)就是對 WebBackForwardCache – 前進(jìn)后退緩存的封裝,本質(zhì)上是瀏覽歷史的一種記錄,不屬于上述標(biāo)準(zhǔn)緩存。前進(jìn)后退緩存,將整個頁面快照存入到內(nèi)存中,下一次使用的時(shí)候,不用進(jìn)行各類資源加載,甚至不用進(jìn)行渲染工作。
通過源碼查看,pageCache 大小會隨著可使用內(nèi)存大小動態(tài)變化:
| 手機(jī)可用內(nèi)存a | 可緩存page頁數(shù) |
| a >= 512M | 2 |
| 512M > a >= 256M | 1 |
| other | 0 |
緩存策略源碼如下所示:
// back/forward cache capacity (in pages)if (memorySize >= 512)backForwardCacheCapacity = 2;else if (memorySize >= 256)backForwardCacheCapacity = 1;elsebackForwardCacheCapacity = 0;
資源的過期時(shí)間默認(rèn)為30分鐘。通過定時(shí)器觸發(fā)任務(wù),30分鐘后自動清理過期的 page。源碼如下:
static const Seconds expirationDelay { 30_min };//通過定時(shí)器觸發(fā),到過期時(shí)間后,進(jìn)行資源清理void WebBackForwardCacheEntry::expirationTimerFired(){RELEASE_LOG(BackForwardCache, "%p - WebBackForwardCacheEntry::expirationTimerFired backForwardItemID=%s, hasSuspendedPage=%d", this, m_backForwardItemID.string().utf8().data(), !!m_suspendedPage);ASSERT(m_backForwardItemID);auto* item = WebBackForwardListItem::itemForID(m_backForwardItemID);ASSERT(item);m_backForwardCache.removeEntry(*item); // Will destroy |this|.}
因 pageCache 存儲頁面數(shù)量有限,因此當(dāng)超出頁面緩存上限時(shí),需要通過如下 LRU 算法進(jìn)行替換:
void BackForwardCache::prune(PruningReason pruningReason){while (pageCount() > maxSize()) {auto oldestItem = m_items.takeFirst();oldestItem->setCachedPage(nullptr);oldestItem->m_pruningReason = pruningReason;RELEASE_LOG(BackForwardCache, "BackForwardCache::prune removing item: %s, size: %u / %u", oldestItem->identifier().string().utf8().data(), pageCount(), maxSize());}}
緩存時(shí)機(jī)源碼如下:
bool WebPageProxy::suspendCurrentPageIfPossible(...) {...// If the source and the destination back / forward list items are the same, then this is a client-side redirect. In this case,// there is no need to suspend the previous page as there will be no way to get back to it.if (fromItem && fromItem == m_backForwardList->currentItem()) {RELEASE_LOG_IF_ALLOWED(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i because this is a client-side redirect", m_process->processIdentifier());return false;}...//創(chuàng)建 SuspendedPageProxy 變量,此時(shí) m_suspendedPageCount 的值會加一auto suspendedPage = makeUnique<SuspendedPageProxy>(*this, m_process.copyRef(), *mainFrameID, shouldDelayClosingUntilFirstLayerFlush);m_lastSuspendedPage = makeWeakPtr(*suspendedPage);...//添加進(jìn)歷史棧緩存backForwardCache().addEntry(*fromItem, WTFMove(suspendedPage));...}
可以看到,如果 WKWebView 切換頁面時(shí),發(fā)生 cross-site 且為 client-side redirect 時(shí)會清理當(dāng)前 WebProgressProxy 關(guān)聯(lián)的所有歷史棧緩存,后續(xù)切換到這些歷史棧時(shí)都需要重新請求網(wǎng)絡(luò)。而其他類型都會正常存儲,因此可以基于前進(jìn)后退相關(guān)操作的頁面性能考慮,可以減少前端重定向,多依賴后端進(jìn)行重定向功能。
丨2.2 緩存清理方式
處于內(nèi)存中的緩存,會隨著進(jìn)程的結(jié)束而消亡。而處于磁盤中的緩存,則可以通過如下方法進(jìn)行手動清理,避免磁盤占用增長過大。
2.2.1 文件目錄清理方式
webkit磁盤中的較多數(shù)據(jù)都是通過域名做為文件名的一部分,因此也可以通過域名、日期等方式匹配,進(jìn)行文件刪除:
NSString *libraryDir = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES)[0];NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];NSString *webkitFolderInLib = [NSString stringWithFormat:@"%@/WebKit",libraryDir];NSString *webKitFolderInCaches = [NSString stringWithFormat:@"%@/Caches/%@/WebKit",libraryDir,bundleId];NSError *error;[[NSFileManager defaultManager] removeItemAtPath:webKitFolderInCaches error:&error];[[NSFileManager defaultManager] removeItemAtPath:webkitFolderInLib error:nil];

localStorage 存儲文件樣例
2.2.2 緩存類型清理方式
iOS 9.0以后 , WebKit 清除緩存的API,測試來看必須在主線程進(jìn)行操作。
NSSet *websiteDataTypes = [NSSet setWithArray:@[WKWebsiteDataTypeDiskCache,WKWebsiteDataTypeOfflineWebApplicationCache,WKWebsiteDataTypeLocalStorage,WKWebsiteDataTypeCookies,WKWebsiteDataTypeSessionStorage,WKWebsiteDataTypeIndexedDBDatabases,WKWebsiteDataTypeWebSQLDatabases]];NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];//dataTypes: 指定刪除的網(wǎng)站數(shù)據(jù)類型,date: 在此日期之后修改的所有網(wǎng)站數(shù)據(jù)將被刪除,completionHandler: 當(dāng)網(wǎng)站數(shù)據(jù)被刪除時(shí)調(diào)用的block。[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{// 結(jié)束回調(diào)}];
3. HTTP 內(nèi)存緩存
WKWebView 與 app 處于不同進(jìn)程中,且內(nèi)存與磁盤緩存也在不同進(jìn)程中,其中,內(nèi)存緩存位于 WebContentProcess 進(jìn)程中,而磁盤緩存位于 NetworkProcess 進(jìn)程中。且每個memoryCache 對應(yīng)一個 webContent 進(jìn)程,如圖所示。

如上圖所示,一個頁面對應(yīng)一個WebContentProcess 進(jìn)程,當(dāng)頁面銷毀時(shí),其對應(yīng)的內(nèi)存緩存也被銷毀。
丨3.1 內(nèi)存緩存大小
雖然 WebKit 進(jìn)程獨(dú)立與 app 進(jìn)程,但內(nèi)存占用過大依舊會影響到 app 進(jìn)程的性能,因此內(nèi)存緩存根據(jù)手機(jī)當(dāng)前緩存大小進(jìn)行分配。
| 手機(jī)可用內(nèi)存a | 頁面內(nèi)存分配 |
| a >= 2G | 128M |
| 2G > a >= 1.5G | 96M |
| 1.5G > a >= 1G | 64M |
| 1G > a >= 0.5G | 32M |
| other | 16M |
緩存大小計(jì)算策略源碼如下:
case CacheModel::PrimaryWebBrowser: {// back/forward cache capacity (in pages)if (memorySize >= 512)backForwardCacheCapacity = 2;else if (memorySize >= 256)backForwardCacheCapacity = 1;elsebackForwardCacheCapacity = 0;// Object cache capacities (in bytes)// (Testing indicates that value / MB depends heavily on content and// browsing pattern. Even growth above 128MB can have substantial// value / MB for some content / browsing patterns.)if (memorySize >= 2048)cacheTotalCapacity = 128 * MB;else if (memorySize >= 1536)cacheTotalCapacity = 96 * MB;else if (memorySize >= 1024)cacheTotalCapacity = 64 * MB;else if (memorySize >= 512)cacheTotalCapacity = 32 * MB;elsecacheTotalCapacity = 16 * MB;cacheMinDeadCapacity = cacheTotalCapacity / 4;cacheMaxDeadCapacity = cacheTotalCapacity / 2;// This code is here to avoid a PLT regression. We can remove it if we// can prove that the overall system gain would justify the regression.cacheMaxDeadCapacity = std::max(24u, cacheMaxDeadCapacity);deadDecodedDataDeletionInterval = 60_s;break;}
丨3.2 內(nèi)存緩存策略
使用 map 字典,在內(nèi)存中使用 url 為 key,resource 資源為 value,對當(dāng)前頁面的所有 HTTP 網(wǎng)絡(luò)請求資源進(jìn)行存儲。
3.2.1 內(nèi)存緩存添加
bool MemoryCache::add(CachedResource& resource){if (disabled())return false;if (resource.resourceRequest().httpMethod() != "GET")return false;ASSERT(WTF::isMainThread());auto key = std::make_pair(resource.url(), resource.cachePartition());ensureSessionResourceMap(resource.sessionID()).set(key, &resource);resource.setInCache(true);resourceAccessed(resource);LOG(ResourceLoading, "MemoryCache::add Added '%.255s', resource %p\n", resource.url().string().latin1().data(), &resource);return true;}
3.2.2 內(nèi)存緩存讀取
CachedResource* MemoryCache::resourceForRequest(const ResourceRequest& request, PAL::SessionID sessionID){// FIXME: Change all clients to make sure HTTP(s) URLs have no fragment identifiers before calling here.// CachedResourceLoader is now doing this. Add an assertion once all other clients are doing it too.auto* resources = sessionResourceMap(sessionID);if (!resources)return nullptr;return resourceForRequestImpl(request, *resources);}CachedResource* MemoryCache::resourceForRequestImpl(const ResourceRequest& request, CachedResourceMap& resources){ASSERT(WTF::isMainThread());URL url = removeFragmentIdentifierIfNeeded(request.url());auto key = std::make_pair(url, request.cachePartition());return resources.get(key);}
3.2.3 HTTP 內(nèi)存緩存讀取策略
HTTP 內(nèi)存緩存讀取時(shí)機(jī)不同于磁盤緩存,它并不完全遵守 HTTP 標(biāo)準(zhǔn)協(xié)議,而是根據(jù)瀏覽器所加載的資源策略來進(jìn)行的。例如:
前進(jìn)后退歷史中的頁面的資源請求,可以直接讀取內(nèi)存緩存。
圖片資源使用同一 url 加載的時(shí)候,后續(xù)的資源會 block 住,等待首個圖片資源的返回,直接使用緩存。
preload 資源可直接讀取緩存,不必進(jìn)行任何判斷。
// 網(wǎng)絡(luò)請求加載是否使用內(nèi)存緩存有如下策略:enum RevalidationPolicy {Use, // 直接使用Revalidate, // 需要經(jīng)過 HTTP 緩存協(xié)議校驗(yàn)Reload, // 重新加載,清理內(nèi)存緩存,并重新請求Load // 直接從網(wǎng)絡(luò)加載};RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, imageLoading);
4. HTTP 磁盤緩存
磁盤緩存的設(shè)計(jì)完全遵循 HTTP 標(biāo)準(zhǔn)緩存協(xié)議。所有的網(wǎng)絡(luò)請求都經(jīng)過 NetWorkProcess 進(jìn)程發(fā)出,請求在發(fā)出之前,則會經(jīng)過緩存協(xié)議檢驗(yàn),根據(jù) HTTP 協(xié)議進(jìn)行相應(yīng)操作(讀取緩存/協(xié)商檢驗(yàn)/不使用緩存等)。當(dāng)服務(wù)端返回請求內(nèi)容后,NetworkProcess 模塊也會做出對應(yīng)的判斷,決定內(nèi)容是否進(jìn)行緩存或更新,如下所示。
丨4.1 HTTP 緩存處理流程圖

丨4.2 HTTP 磁盤緩存大小
磁盤緩存存入到指定的文件目錄中,其中默認(rèn)為:Library/Caches/WebKit/NetworkCache。可以通過如下方法進(jìn)行指定:
case CacheModel::PrimaryWebBrowser: {// Disk cache capacity (in bytes)if (diskFreeSize >= 16384)urlCacheDiskCapacity = 1 * GB;else if (diskFreeSize >= 8192)urlCacheDiskCapacity = 500 * MB;else if (diskFreeSize >= 4096)urlCacheDiskCapacity = 250 * MB;else if (diskFreeSize >= 2048)urlCacheDiskCapacity = 200 * MB;else if (diskFreeSize >= 1024)urlCacheDiskCapacity = 150 * MB;elseurlCacheDiskCapacity = 100 * MB;break;}default:ASSERT_NOT_REACHED();};
丨4.3 HTTP 存入緩存校驗(yàn)
本部分主要根據(jù)請求和響應(yīng)來判斷是否需要存儲到緩存中。主要判斷 scheme、method 以及資源的緩存策略。
// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cppstatic StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, size_t bodySize){if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isInHTTPFamily())return StoreDecision::NoDueToProtocol;if (originalRequest.httpMethod() != "GET")return StoreDecision::NoDueToHTTPMethod;auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields());if (requestDirectives.noStore)return StoreDecision::NoDueToNoStoreRequest;if (response.cacheControlContainsNoStore())return StoreDecision::NoDueToNoStoreResponse;if (!WebCore::isStatusCodeCacheableByDefault(response.httpStatusCode())) {// http://tools.ietf.org/html/rfc7234#section-4.3.2bool hasExpirationHeaders = response.expires() || response.cacheControlMaxAge();bool expirationHeadersAllowCaching = WebCore::isStatusCodePotentiallyCacheable(response.httpStatusCode()) && hasExpirationHeaders;if (!expirationHeadersAllowCaching)return StoreDecision::NoDueToHTTPStatusCode;}bool isMainResource = originalRequest.requester() == WebCore::ResourceRequest::Requester::Main;bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority() == WebCore::ResourceLoadPriority::VeryHigh;if (!storeUnconditionallyForHistoryNavigation) {auto now = WallTime::now();Seconds allowedStale { 0_ms };#if ENABLE(NETWORK_CACHE_STALE_WHILE_REVALIDATE)if (auto value = response.cacheControlStaleWhileRevalidate())allowedStale = value.value();#endifbool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && (WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms || allowedStale > 0_ms);bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;if (!possiblyReusable)return StoreDecision::NoDueToUnlikelyToReuse;}// Media loaded via XHR is likely being used for MSE streaming (YouTube and Netflix for example).// Streaming media fills the cache quickly and is unlikely to be reused.// FIXME: We should introduce a separate media cache partition that doesn't affect other resources.// FIXME: We should also make sure make the MSE paths are copy-free so we can use mapped buffers from disk effectively.auto requester = originalRequest.requester();bool isDefinitelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::Media;bool isLikelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::XHR && isMediaMIMEType(response.mimeType());if (isLikelyStreamingMedia || isDefinitelyStreamingMedia)return StoreDecision::NoDueToStreamingMedia;return StoreDecision::Yes;}
丨4.4 HTTP 讀取緩存校驗(yàn)
本部分主要根據(jù)請求來判斷是否去緩存中讀取緩存。主要判斷 scheme、method 以及資源的緩存策略。
// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cppstatic RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request){ASSERT(request.cachePolicy() != WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache);// FIXME: Support HEAD requests.if (request.httpMethod() != "GET")return RetrieveDecision::NoDueToHTTPMethod;if (request.cachePolicy() == WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData && !request.isConditional())return RetrieveDecision::NoDueToReloadIgnoringCache;return RetrieveDecision::Yes;}
丨4.5 HTTP 使用緩存校驗(yàn)
本部分主要根據(jù)請求和響應(yīng)來判斷緩存是否可以直接使用。主要根據(jù)緩存字段計(jì)算當(dāng)前的資源是否過期。
// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cppstatic UseDecision makeUseDecision(NetworkProcess& networkProcess, const PAL::SessionID& sessionID, const Entry& entry, const WebCore::ResourceRequest& request){// The request is conditional so we force revalidation from the network. We merely check the disk cache// so we can update the cache entry.// 條件請求判斷 | bool ResourceRequestBase::isConditionalif (request.isConditional() && !entry.redirectRequest())return UseDecision::Validate;// 校驗(yàn)變化的請求頭 | verifyVaryingRequestHeadersif (!WebCore::verifyVaryingRequestHeaders(networkProcess.storageSession(sessionID), entry.varyingRequestHeaders(), request))return UseDecision::NoDueToVaryingHeaderMismatch;// We never revalidate in the case of a history navigation.// 校驗(yàn)緩存是否過期 | cachePolicyAllowsExpiredif (cachePolicyAllowsExpired(request.cachePolicy()))return UseDecision::Use;// 驗(yàn)證請求是否過期auto decision = responseNeedsRevalidation(*networkProcess.networkSession(sessionID), entry.response(), request, entry.timeStamp());if (decision != UseDecision::Validate)return decision;// 驗(yàn)證緩存有效字端(Etag等) | bool ResourceResponseBase::hasCacheValidatorFields()if (!entry.response().hasCacheValidatorFields())return UseDecision::NoDueToMissingValidatorFields;return entry.redirectRequest() ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate;}
4.5.1 HTTP 緩存新鮮度計(jì)算
本部分主要根據(jù)緩存字段計(jì)算當(dāng)前的資源的新鮮度。
// WebKit/Source/WebCore/platform/network/CacheValidation.cppSeconds computeFreshnessLifetimeForHTTPFamily(const ResourceResponse& response, WallTime responseTime){if (!response.url().protocolIsInHTTPFamily())return 0_us;// Freshness Lifetime:// http://tools.ietf.org/html/rfc7234#section-4.2.1auto maxAge = response.cacheControlMaxAge();if (maxAge)return *maxAge;auto date = response.date();auto effectiveDate = date.valueOr(responseTime);if (auto expires = response.expires())return *expires - effectiveDate;// Implicit lifetime.switch (response.httpStatusCode()) {case 301: // Moved Permanentlycase 410: // Gone// These are semantically permanent and so get long implicit lifetime.return 24_h * 365;default:// Heuristic Freshness:// http://tools.ietf.org/html/rfc7234#section-4.2.2if (auto lastModified = response.lastModified())return (effectiveDate - *lastModified) * 0.1;return 0_us;}}
4.5.2 HTTP 緩存新鮮度計(jì)算
本部分主要根據(jù)緩存字段計(jì)算當(dāng)前的資源是否過期。
// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cppstatic UseDecision responseNeedsRevalidation(NetworkSession& networkSession, const WebCore::ResourceResponse& response, WallTime timestamp, Optional<Seconds> maxStale){if (response.cacheControlContainsNoCache())return UseDecision::Validate;// 當(dāng)前過去的時(shí)間 = 當(dāng)前時(shí)間 - 資源時(shí)間 | computeCurrentAgeauto age = WebCore::computeCurrentAge(response, timestamp);// 調(diào)用資源有效時(shí)間計(jì)算 | computeFreshnessLifetimeForHTTPFamilyauto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);// 資源允許過期時(shí)間auto maximumStaleness = maxStale ? maxStale.value() : 0_ms;// qy6_detail 資源是否超期 | 當(dāng)前過去的時(shí)間 - 資源有效時(shí)間 - 允許過期時(shí)間 > 0 => 資源過期了bool hasExpired = age - lifetime > maximumStaleness;#if ENABLE(NETWORK_CACHE_STALE_WHILE_REVALIDATE)if (hasExpired && !maxStale && networkSession.isStaleWhileRevalidateEnabled()) {auto responseMaxStaleness = response.cacheControlStaleWhileRevalidate();maximumStaleness += responseMaxStaleness ? responseMaxStaleness.value() : 0_ms;bool inResponseStaleness = age - lifetime < maximumStaleness;if (inResponseStaleness)return UseDecision::AsyncRevalidate;}#endifif (hasExpired) {#ifndef LOG_DISABLEDLOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-staleness=%f", age, lifetime, maximumStaleness);#endifreturn UseDecision::Validate;}return UseDecision::Use;}
丨4.6 HTTP 服務(wù)器資源校驗(yàn)
過期資源需要從服務(wù)器判斷是否可用,需要構(gòu)造一個條件請求去服務(wù)端驗(yàn)證當(dāng)前過期資源是否可用。
// WebKit/Source/WebKit/NetworkProcess/NetworkResourceLoader.cppvoid NetworkResourceLoader::validateCacheEntry(std::unique_ptr<NetworkCache::Entry> entry){RELEASE_LOG_IF_ALLOWED("validateCacheEntry:");ASSERT(!m_networkLoad);// If the request is already conditional then the revalidation was not triggered by the disk cache// and we should not overwrite the existing conditional headers.// 如果請求為條件請求,不修改 HEADER 中條件請求屬性ResourceRequest revalidationRequest = originalRequest();if (!revalidationRequest.isConditional()) {String eTag = entry->response().httpHeaderField(HTTPHeaderName::ETag);String lastModified = entry->response().httpHeaderField(HTTPHeaderName::LastModified);// qy6_detail 新增緩存校驗(yàn)請求頭,IfNoneMatch 和 IfModifiedSinceif (!eTag.isEmpty())revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);if (!lastModified.isEmpty())revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);}m_cacheEntryForValidation = WTFMove(entry);// qy6_detail 發(fā)起請求startNetworkLoad(WTFMove(revalidationRequest), FirstLoad::Yes);}
丨4.7 HTTP 緩存資源更新
當(dāng)服務(wù)器驗(yàn)證通過后,需要對現(xiàn)有的緩存資源進(jìn)行更新,緩存資源更新后返回給客戶端。
// WebKit/Source/WebCore/platform/network/CacheValidation.cppvoid updateResponseHeadersAfterRevalidation(ResourceResponse& response, const ResourceResponse& validatingResponse){// Freshening stored response upon validation:// http://tools.ietf.org/html/rfc7234#section-4.3.4for (const auto& header : validatingResponse.httpHeaderFields()) {// Entity headers should not be sent by servers when generating a 304// response; misconfigured servers send them anyway. We shouldn't allow// such headers to update the original request. We'll base this on the// list defined by RFC2616 7.1, with a few additions for extension headers// we care about.// 是否應(yīng)該更新請求頭if (!shouldUpdateHeaderAfterRevalidation(header.key))continue;response.setHTTPHeaderField(header.key, header.value);}}
參考資料
1. 《HTTP 權(quán)威指南》
2. "HTTP 緩存 - HTTP | MDN"
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ
3. "Cache-Control - HTTP | MDN"
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
4. "Message Syntax and Routing"
https://tools.ietf.org/html/rfc7230
5. "Semantics and Content"
https://tools.ietf.org/html/rfc7231
6. "Conditional Requests"
https://tools.ietf.org/html/rfc7232
7. "Range Requests"
https://tools.ietf.org/html/rfc7233
8. "Caching"
https://tools.ietf.org/html/rfc7234
9. "Authentication"
https://tools.ietf.org/html/rfc7235
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取