1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 21131字,需瀏覽 43分鐘

         ·

        2022-01-16 17:59

        ????關(guān)注后回復(fù) “進群” ,拉你進程序員交流群????


        作者丨童紅明

        來源丨百度App技術(shù)


        1. 前言

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


        2. 緩存簡介


        2.1 緩存類型


        2.1.1 WebKit 標準緩存類型

        // HTTP 磁盤緩存。WKWebsiteDataTypeDiskCache,
        // html離線Web應(yīng)用程序緩存。WKWebsiteDataTypeOfflineWebApplicationCache,
        // HTTP 內(nèi)存緩存。WKWebsiteDataTypeMemoryCache,
        // 會話存儲:存儲對數(shù)據(jù)只有在同一個會話中的頁面才能訪問并且當會話結(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ù)較大比例,可達80%以上。WebKit 磁盤緩存分布如下表:

        磁盤文件目錄
        緩存類型
        Library/WebKit

        IndexedDB 

        LocalStorage

        MediaKeys

        ResourceLoadStatistics

        Library/Caches/WebKit

        CacheStorage

        NetworkCache

        offlineWebApplicationCache

        ServiceWorkers


        2.1.2 前進后退緩存 – pageCache

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

        通過源碼查看,pageCache 大小會隨著可使用內(nèi)存大小動態(tài)變化:

        手機可用內(nèi)存a
        可緩存page頁數(shù)
        a >= 512M2
        512M > a >= 256M1
        other0

        緩存策略源碼如下所示:

        // back/forward cache capacity (in pages)if (memorySize >= 512)    backForwardCacheCapacity = 2;else if (memorySize >= 256)    backForwardCacheCapacity = 1;else    backForwardCacheCapacity = 0;

        資源的過期時間默認為30分鐘。通過定時器觸發(fā)任務(wù),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 關(guān)聯(lián)的所有歷史棧緩存,后續(xù)切換到這些歷史棧時都需要重新請求網(wǎng)絡(luò)。而其他類型都會正常存儲,因此可以基于前進后退相關(guān)操作的頁面性能考慮,可以減少前端重定向,多依賴后端進行重定向功能。

        2.2 緩存清理方式

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

        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];

        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ù)被刪除時調(diào)用的block。[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ // 結(jié)束回調(diào) }];


        3. HTTP 內(nèi)存緩存


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





        如上圖所示,一個頁面對應(yīng)一個WebContentProcess 進程,當頁面銷毀時,其對應(yīng)的內(nèi)存緩存也被銷毀。

        3.1 內(nèi)存緩存大小

        雖然 WebKit 進程獨立與 app 進程,但內(nèi)存占用過大依舊會影響到 app 進程的性能,因此內(nèi)存緩存根據(jù)手機當前緩存大小進行分配。

        手機可用內(nèi)存a
        頁面內(nèi)存分配
        a >= 2G128M
        2G > a >= 1.5G96M
        1.5G > a >= 1G64M
        1G > a >= 0.5G32M
        other16M

        緩存大小計算策略源碼如下:

        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 內(nèi)存緩存策略

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

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

        1. 前進后退歷史中的頁面的資源請求,可以直接讀取內(nèi)存緩存。

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

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

        // 網(wǎng)絡(luò)請求加載是否使用內(nèi)存緩存有如下策略:enum RevalidationPolicy { Use, // 直接使用Revalidate, // 需要經(jīng)過 HTTP 緩存協(xié)議校驗Reload, // 重新加載,清理內(nèi)存緩存,并重新請求Load // 直接從網(wǎng)絡(luò)加載};
        RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, imageLoading);


        4. HTTP 磁盤緩存


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

        4.1 HTTP 緩存處理流程圖




        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ù)請求和響應(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.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ù)請求和響應(yīng)來判斷緩存是否可以直接使用。主要根據(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); // 調(diào)用資源有效時間計算 | 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 服務(wù)器資源校驗

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

        // 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 緩存資源更新 

        當服務(wù)器驗證通過后,需要對現(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.         // 是否應(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)盤了,歡迎下載!

        點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

        在看點這里好文分享給更多人↓↓

        瀏覽 82
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            色欧美在线| 办公室三级hd激情 | 国产精品永久在线 | 不知火舞扒开胸罩给男生动漫 | 精品极品在线 | 天天干天天操天天射黄色电影 | 盗摄精品av一区二区三区 | 做爱网站网站入口免费观看 | 成人片黄网站A片免费 | haodri成人无码免费视频 |