深入理解 WKWebView(基礎篇)-- 探究 WebKit 緩存

1. 前言

緩存可以減少冗余的數(shù)據(jù)傳輸,解決網(wǎng)絡瓶頸問題,降低服務端壓力,提升頁面加載速度。高效利用緩存可大幅提升頁面加載速度,提升用戶的瀏覽體驗。WKWebView 使用緩存技術存儲前后端資源,用戶提高頁面性能和用戶體驗。因為 WKWebView 的封閉性,我們無法針對原生 WKWebView 做較深度化的定制,但對于 WebKit 緩存源碼的探究,將幫助我們更好的使用和理解緩存。本文將延續(xù) 《iOS 端 webkit 源碼調試與分析》的思路,結合源碼枚舉 WKWebView 中的各類緩存,并重點講述其中的 HTTP 協(xié)議緩存,幫助讀者更好的理解 WebKit 中緩存的設計思路。

2. 緩存簡介

2.1 緩存類型

2.1.1 WebKit 標準緩存類型
// HTTP 磁盤緩存。
WKWebsiteDataTypeDiskCache,

// html離線Web應用程序緩存。
WKWebsiteDataTypeOfflineWebApplicationCache, 

// HTTP 內存緩存。
WKWebsiteDataTypeMemoryCache, 

// 會話存儲:存儲對數(shù)據(jù)只有在同一個會話中的頁面才能訪問并且當會話結束后數(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ù)庫接口操組復雜,對用戶不友好。
WKWebsiteDataTypeWebSQLDatabases

通過數(shù)據(jù)分析,主要是 indexedDB 與 NetworkCache 占據(jù)較大比例,可達80%以上。WebKit 磁盤緩存分布如下表:
[磁盤文件目錄][緩存類型]
Library/WebKit ——IndexedDB
———————————————LocalStorage
———————————————MediaKeys
———————————————ResourceLoadStatistics
Library/Caches/WebKit —CacheStorage
———————————————NetworkCache
———————————————offlineWebApplicationCache
———————————————ServiceWorkers

2.1.2 前進后退緩存 – pageCache

在 WebKit 中,pageCache 其實就是對 WebBackForwardCache – 前進后退緩存的封裝,本質上是瀏覽歷史的一種記錄,不屬于上述標準緩存。前進后退緩存,將整個頁面快照存入到內存中,下一次使用的時候,不用進行各類資源加載,甚至不用進行渲染工作。

通過源碼查看,pageCache 大小會隨著可使用內存大小動態(tà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;
else
    backForwardCacheCapacity = 0;

資源的過期時間默認為30分鐘。通過定時器觸發(fā)任務,30分鐘后自動清理過期的 page。源碼如下:

static const Seconds expirationDelay { 30_min }; 
//通過定時器觸發(fā),到過期時間后,進行資源清理 
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ù)量有限,因此當超出頁面緩存上限時,需要通過如下 LRU 算法進行替換:

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());
    }
}

緩存時機源碼如下:

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 變量,此時 m_suspendedPageCount 的值會加一 
    auto suspendedPage = makeUnique<SuspendedPageProxy>(*this, m_process.copyRef(), *mainFrameID, shouldDelayClosingUntilFirstLayerFlush); 
    m_lastSuspendedPage = makeWeakPtr(*suspendedPage); 
    ...
    //添加進歷史棧緩存 
    backForwardCache().addEntry(*fromItem, WTFMove(suspendedPage)); 
    ...
}

可以看到,如果 WKWebView 切換頁面時,發(fā)生 cross-site 且為 client-side redirect 時會清理當前 WebProgressProxy 關聯(lián)的所有歷史棧緩存,后續(xù)切換到這些歷史棧時都需要重新請求網(wǎng)絡。而其他類型都會正常存儲,因此可以基于前進后退相關操作的頁面性能考慮,可以減少前端重定向,多依賴后端進行重定向功能。

2.2 緩存清理方式

處于內存中的緩存,會隨著進程的結束而消亡。而處于磁盤中的緩存,則可以通過如下方法進行手動清理,避免磁盤占用增長過大。

2.2.1 文件目錄清理方式

webkit磁盤中的較多數(shù)據(jù)都是通過域名做為文件名的一部分,因此也可以通過域名、日期等方式匹配,進行文件刪除:

 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];
image.png

localStorage 存儲文件樣例

2.2.2 緩存類型清理方式

iOS 9.0以后 , WebKit 清除緩存的API,測試來看必須在主線程進行操作。

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: 當網(wǎng)站數(shù)據(jù)被刪除時調用的block。
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ 
// 結束回調 
}];

3. HTTP 內存緩存

WKWebView 與 app 處于不同進程中,且內存與磁盤緩存也在不同進程中,其中,內存緩存位于 WebContentProcess 進程中,而磁盤緩存位于 NetworkProcess 進程中。且每個memoryCache 對應一個 webContent 進程,如圖所示。


image.png

如上圖所示,一個頁面對應一個WebContentProcess 進程,當頁面銷毀時,其對應的內存緩存也被銷毀。

3.1 內存緩存大小

雖然 WebKit 進程獨立與 app 進程,但內存占用過大依舊會影響到 app 進程的性能,因此內存緩存根據(jù)手機當前緩存大小進行分配。
手機可用內存a 頁面內存分配
a >= 2G 128M
2G > a >= 1.5G 96M
1.5G > a >= 1G 64M
1G > a >= 0.5G 32M
other 16M
緩存大小計算策略源碼如下:

case CacheModel::PrimaryWebBrowser: { 
    // back/forward cache capacity (in pages) 
    if (memorySize >= 512) 
        backForwardCacheCapacity = 2; 
    else if (memorySize >= 256) 
        backForwardCacheCapacity = 1; 
    else 
        backForwardCacheCapacity = 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; 
    else 
        cacheTotalCapacity = 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 內存緩存策略

使用 map 字典,在內存中使用 url 為 key,resource 資源為 value,對當前頁面的所有 HTTP 網(wǎng)絡請求資源進行存儲。

3.2.1 內存緩存添加
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 內存緩存讀取
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 內存緩存讀取策略

HTTP 內存緩存讀取時機不同于磁盤緩存,它并不完全遵守 HTTP 標準協(xié)議,而是根據(jù)瀏覽器所加載的資源策略來進行的。例如:

前進后退歷史中的頁面的資源請求,可以直接讀取內存緩存。

圖片資源使用同一 url 加載的時候,后續(xù)的資源會 block 住,等待首個圖片資源的返回,直接使用緩存。

preload 資源可直接讀取緩存,不必進行任何判斷。

// 網(wǎng)絡請求加載是否使用內存緩存有如下策略:
enum RevalidationPolicy { 
Use, // 直接使用
Revalidate, // 需要經(jīng)過 HTTP 緩存協(xié)議校驗
Reload, // 重新加載,清理內存緩存,并重新請求
Load // 直接從網(wǎng)絡加載
};

RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, imageLoading);

4. HTTP 磁盤緩存

磁盤緩存的設計完全遵循 HTTP 標準緩存協(xié)議。所有的網(wǎng)絡請求都經(jīng)過 NetWorkProcess 進程發(fā)出,請求在發(fā)出之前,則會經(jīng)過緩存協(xié)議檢驗,根據(jù) HTTP 協(xié)議進行相應操作(讀取緩存/協(xié)商檢驗/不使用緩存等)。當服務端返回請求內容后,NetworkProcess 模塊也會做出對應的判斷,決定內容是否進行緩存或更新,如下所示。

4.1 HTTP 緩存處理流程圖

image.png

4.2 HTTP 磁盤緩存大小

磁盤緩存存入到指定的文件目錄中,其中默認為:

Library/Caches/WebKit/NetworkCache??梢酝ㄟ^如下方法進行指定:
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; 
        else 
            urlCacheDiskCapacity = 100 * MB; 

        break; 
    }
    default: 
        ASSERT_NOT_REACHED(); 
    };

4.3 HTTP 存入緩存校驗

本部分主要根據(jù)請求和響應來判斷是否需要存儲到緩存中。主要判斷 scheme、method 以及資源的緩存策略。

// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp
static 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.2
        bool 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();
#endif
        bool 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 讀取緩存校驗

本部分主要根據(jù)請求來判斷是否去緩存中讀取緩存。主要判斷 scheme、method 以及資源的緩存策略。

// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp 
static 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 使用緩存校驗

本部分主要根據(jù)請求和響應來判斷緩存是否可以直接使用。主要根據(jù)緩存字段計算當前的資源是否過期。

// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp 
static 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::isConditional 
    if (request.isConditional() && !entry.redirectRequest()) 
        return UseDecision::Validate; 


    // 校驗變化的請求頭 | verifyVaryingRequestHeaders 
    if (!WebCore::verifyVaryingRequestHeaders(networkProcess.storageSession(sessionID), entry.varyingRequestHeaders(), request)) 
        return UseDecision::NoDueToVaryingHeaderMismatch; 


    // We never revalidate in the case of a history navigation. 
    // 校驗緩存是否過期 | cachePolicyAllowsExpired 
    if (cachePolicyAllowsExpired(request.cachePolicy()))  
        return UseDecision::Use;


    // 驗證請求是否過期 
    auto decision = responseNeedsRevalidation(*networkProcess.networkSession(sessionID), entry.response(), request, entry.timeStamp()); 
    if (decision != UseDecision::Validate) 
        return decision; 


    // 驗證緩存有效字端(Etag等) | bool ResourceResponseBase::hasCacheValidatorFields() 
    if (!entry.response().hasCacheValidatorFields()) 
        return UseDecision::NoDueToMissingValidatorFields; 


    return entry.redirectRequest() ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate; 
}
4.5.1 HTTP 緩存新鮮度計算

本部分主要根據(jù)緩存字段計算當前的資源的新鮮度。

// WebKit/Source/WebCore/platform/network/CacheValidation.cpp 
Seconds computeFreshnessLifetimeForHTTPFamily(const ResourceResponse& response, WallTime responseTime) 
{
    if (!response.url().protocolIsInHTTPFamily())
        return 0_us; 


    // Freshness Lifetime: 
    // http://tools.ietf.org/html/rfc7234#section-4.2.1 
    auto 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 Permanently 
    case 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.2 
        if (auto lastModified = response.lastModified()) 
            return (effectiveDate - *lastModified) * 0.1; 
        return 0_us; 
    }
}
4.5.2 HTTP 緩存新鮮度計算

本部分主要根據(jù)緩存字段計算當前的資源是否過期。

// WebKit/Source/WebKit/NetworkProcess/cache/NetworkCache.cpp 
static UseDecision responseNeedsRevalidation(NetworkSession& networkSession, const WebCore::ResourceResponse& response, WallTime timestamp, Optional<Seconds> maxStale) 
{
    if (response.cacheControlContainsNoCache()) 
        return UseDecision::Validate; 


    // 當前過去的時間 = 當前時間 - 資源時間 | computeCurrentAge 
    auto age = WebCore::computeCurrentAge(response, timestamp); 
    // 調用資源有效時間計算 | computeFreshnessLifetimeForHTTPFamily 
    auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp); 


    // 資源允許過期時間 
    auto maximumStaleness = maxStale ? maxStale.value() : 0_ms; 
    // qy6_detail 資源是否超期 | 當前過去的時間 - 資源有效時間 - 允許過期時間 > 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; 
    }
#endif 


    if (hasExpired) { 
#ifndef LOG_DISABLED 
        LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-staleness=%f", age, lifetime, maximumStaleness); 
#endif 
        return UseDecision::Validate; 
    }


    return UseDecision::Use; 
}

4.6 HTTP 服務器資源校驗

過期資源需要從服務器判斷是否可用,需要構造一個條件請求去服務端驗證當前過期資源是否可用。

// WebKit/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp 
void 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 新增緩存校驗請求頭,IfNoneMatch 和 IfModifiedSince 
        if (!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 緩存資源更新

當服務器驗證通過后,需要對現(xiàn)有的緩存資源進行更新,緩存資源更新后返回給客戶端。

// WebKit/Source/WebCore/platform/network/CacheValidation.cpp 
void updateResponseHeadersAfterRevalidation(ResourceResponse& response, const ResourceResponse& validatingResponse) 
{
    // Freshening stored response upon validation: 
    // http://tools.ietf.org/html/rfc7234#section-4.3.4 
    for (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. 
        // 是否應該更新請求頭 
        if (!shouldUpdateHeaderAfterRevalidation(header.key)) 
            continue; 
        response.setHTTPHeaderField(header.key, header.value); 
    }
}

參考資料

  1. 《HTTP 權威指南》
  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
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容