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

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 進程,如圖所示。

如上圖所示,一個頁面對應一個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 緩存處理流程圖

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);
}
}
參考資料
- 《HTTP 權威指南》
- "HTTP 緩存 - HTTP | MDN"
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ - "Cache-Control - HTTP | MDN"
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control - "Message Syntax and Routing"
https://tools.ietf.org/html/rfc7230 - "Semantics and Content"
https://tools.ietf.org/html/rfc7231 - "Conditional Requests"
https://tools.ietf.org/html/rfc7232 - "Range Requests"
https://tools.ietf.org/html/rfc7233 - "Caching"
https://tools.ietf.org/html/rfc7234 - "Authentication"
https://tools.ietf.org/html/rfc7235