Network 进程 URL Cache
是在 WKNetworkSessionDelegate 使用 NSURLSession 处理网络请求,没有实现其 URLSession:dataTask:willCacheResponse:completionHandler:代理,底层会默认写 NSURLCache 缓存。
也就是说网络请求的缓存会存在于 Networking 进程里面。
SchemeHandler 如何网络拦截
- 调用 -[WKWebViewConfiguration -setURLSchemeHandler:forURLScheme:] 注入处理 http / https 的自定义 handler ;
- Hook -[WKWebView handlesURLScheme:] 允许处理 http / https 的 scheme;
- 在自定义 handler 里面实现网络请求并回传;
SchemeHandler 底层链路
SchemeHandler 存储链路
-[WKWebViewConfiguration -setURLSchemeHandler:forURLScheme:]
调用:
PageConfiguration::setURLSchemeHandlerForURLScheme
存储到这里:
HashMap<WTF::String, Ref<WebKit::WebURLSchemeHandler>> m_urlSchemeHandlers;
网络请求发起链路
有网络请求时会走到 SchemeHandler 的 -webView:startURLSchemeTask: 方法,由此在 WebKit 找到调用链路:
//WebContent 进程
DocumentLoader::loadMainResource
CachedResourceLoader::requestResource
CachedResource::load
… WebLoaderStrategy::loadResource
WebLoaderStrategy::tryLoadingUsingURLSchemeHandler
WebURLSchemeHandlerProxy::startNewTask
WebURLSchemeTaskProxy::startLoading
IPC: Messages::WebPageProxy::StartURLSchemeTask
//APP进程
WebPageProxy::startURLSchemeTask
WebPageProxy::startURLSchemeTaskShared
WebURLSchemeHandler::startTask
WebURLSchemeHandlerCocoa::platformStartTask
-[WKURLSchemeHandler webView:startURLSchemeTask:]
自定义 WKURLSchemeHandler 网络回包后回传链路
//APP 进程
-[WKURLSchemeTask didReceiveResponse:]
WebURLSchemeTask::didReceiveResponse
IPC: Messages::WebPage::URLSchemeTaskDidReceiveResponse
//WebContent 进程
WebPage::URLSchemeTaskDidReceiveResponse
WebURLSchemeHandlerProxy::taskDidReceiveResponse
WebURLSchemeTaskProxy::didReceiveResponse
ResourceLoader::didReceiveResponse
ResourceLoadNotifier::didReceiveResponse
DocumentLoader::addResponse
看起来没有传送到 Networking 进程。
Web 进程资源缓存
加载网页资源时会在 CachedResourceLoader 寻找是否有可用缓存:
DocumentLoader::loadMainResource
CachedResourceLoader::requestResource
CachedResourceLoader::determineRevalidationPolicy
若没有命中缓存会执行:
…CachedResourceLoader::loadResource(…) {
…
auto resource = createResource(type, WTFMove(request), sessionID, &cookieJar, settings);
if (resource->allowsCaching())
memoryCache.add(*resource);
…
}
createResource(…)实际上就会返回继承 CachedResource 抽象类的资源对象,能缓存则存入 memoryCache,比如 CachedImage / CachedCSSStyleSheet / CachedScript / CachedSVGDocument,后续链路会发起网络请求。
WKURLSchemeHandler 与 CachedResourceLoader 交叉点
由上面的源码分析知,CachedResourceLoader 会先查找 memoryCache,不可用时调用CachedResource::load加载资源,后续会走到 SchemeHandler 处理链路。
SchemeHandler 资源数据是否利用到了 Web 进程资源缓存
对于服务器返回的 response 和不拦截情况没差异,但如果是客户端构建的 response 可能会有一些问题,最直观的就是响应 http header 可能不一致。
所以我们需要知道客户端构建 response 回传到 Web 进程使用与缓存后,第二次访问到是否还有效。
导致 CachedResourceLoader 的 memoryCache 失效原因很多,在最后一部分发现了端倪(有些资源不会走到这个链路,比如图片资源多半没这个判定):
… CachedResourceLoader::determineRevalidationPolicy(…) {
…
auto revalidationDecision = existingResource->makeRevalidationDecision(cachePolicy);
// Check if the cache headers requires us to revalidate (cache expiration for example).
if (revalidationDecision != CachedResource::RevalidationDecision::No) {
// See if the resource has usable ETag or Last-modified headers.
if (existingResource->canUseCacheValidator()) {
return Revalidate;
}
// No, must reload.
return Reload;
}
return Use;
}
一看就是对重用缓存的有效性判定,客户端构建不会加 ETag / Last-modified 所以不用管,也就是说只要 revalidationDecision 不是 No 就得 Reload,无法返回 Use 让上层直接复用。看下如何判定的:
CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(CachePolicy cachePolicy) const {
…
if (m_response.cacheControlContainsNoCache())
return RevalidationDecision::YesDueToNoCache;
// FIXME: Cache-Control:no-store should prevent storing, not reuse.
if (m_response.cacheControlContainsNoStore())
return RevalidationDecision::YesDueToNoStore;
if (isExpired())
return RevalidationDecision::YesDueToExpired;
return RevalidationDecision::No;
…
}
isExpired()实际上就是根据响应头判定资源是否过期,获取响应头新鲜度的代码是这样的:
Seconds computeFreshnessLifetimeForHTTPFamily(…) {
…
// 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.value_or(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;
}
}
逻辑很清晰,先后获取了多个响应头:Cache-Control : max-age、Expires、Date,后面是特殊响应状态码判定,返回值都是有效时间戳与 responseTime 的时间差。
可见,若想让 URL 资源可被 WebContent 进程复用,客户端构建 response 需要设置有效的 max-age 或 Expires 或 Last-Modified 响应头即可。
总结
对于+[WKBrowsingContextController registerSchemeForCustomProtocol:]拦截方案,它是向 Network 进程的 NSURLSession 注册 NSURLProtocol,对于客户端构建的响应包,同样会由 Network 进程传递到 Web 进程,走上面的 CachedResourceLoader 链路。
明确了有无 WebKit 网络拦截时网络请求和缓存的链路,这将在我们做优化决策时起到作用。
比如通过客户端构建网络回包的优化方向,包括离线包方案、自定义 URL Cache、Web 资源预请求、前端 link preload 等。