<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 21131字,需瀏覽 43分鐘

           ·

          2022-01-16 17:59

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


          作者丨童紅明

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


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

          資源的過期時(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 >= 2G128M
          2G > a >= 1.5G96M
          1.5G > a >= 1G64M
          1G > a >= 0.5G32M
          other16M

          緩存大小計(jì)算策略源碼如下:

          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,對當(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)行的。例如:

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

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

          3. 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; else urlCacheDiskCapacity = 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.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 讀取緩存校驗(yàn)

          本部分主要根據(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 使用緩存校驗(yàn)

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

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

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

          // We never revalidate in the case of a history navigation. // 校驗(yàn)緩存是否過期 | cachePolicyAllowsExpired if (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.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ì)算

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

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

          // 當(dāng)前過去的時(shí)間 = 當(dāng)前時(shí)間 - 資源時(shí)間 | computeCurrentAge auto age = WebCore::computeCurrentAge(response, timestamp); // 調(diào)用資源有效時(shí)間計(jì)算 | computeFreshnessLifetimeForHTTPFamily auto 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; }#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ù)器資源校驗(yàn)

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

          // 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 新增緩存校驗(yàn)請求頭,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 緩存資源更新 

          當(dāng)服務(wù)器驗(yàn)證通過后,需要對現(xiàn)有的緩存資源進(jì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)盤了,歡迎下載!

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

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 82
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.毛片网站 | 大鸡吧插逼逼视频 | 91麻豆精品国产91久久久久久 | 亚洲AV无码精品自拍 | 久久露脸国语精品国产 |