yuhanle / blogbag

我会不断更新这个仓库中的文章
https://latehorse.github.io/
14 stars 1 forks source link

尝试 Cronet on iOS #19

Open yuhanle opened 2 years ago

yuhanle commented 2 years ago

Cronet 是 Chromium 的网络库针对 iOS/Android 等的封装,提供易用接口。

准备

为 iOS 编译 Cronet 的文档在 https://chromium.googlesource.com/chromium/src/+/master/components/cronet/ios/docs/BUILD.md

源码与 Chromium 一起,比较大(超过 16GB),下载需要不少时间,而且由于最近网络不好,gclient sync一直失败(无法访问域名 chrome-infra-packages.appspot.com),因此编译无法进行。

后来在逛 Google 论坛,有个帖子提到一个地址有编译好的库,例如对于大版本号为 79 的包,可在 https://console.cloud.google.com/storage/browser/chromium-cronet/ios?pli=1&prefix=79 找到一个列表。我们需要用 gsutil 将某个版本下载下来,例如 79.0.3921.0 且针对模拟器:

gsutil -m cp -r gs://chromium-cronet/ios/79.0.3921.0/Debug-iphonesimulator/ ./

之后,我们在 Debug-iphonesimulator 可找到 Cronet.framework。

建立 Demo 工程,使其依赖 Cronet.framework,阅读头文件 Cronet.h 并编写测试代码。

原理及使用

Cronet 利用 URLSessionConfiguration 的 protocolClasses 属性,将其自定义 URLProtocol CRNHTTPProtocolHandler 注入,它将接管由此 congfiguration 建立的 URLSession 所发起的的所有请求,参考 API installIntoSessionConfiguration。之后,我们可以使用此 session 发起请求,就和正常使用 URLSession 一样,只不过其内部的网络栈被替换为了 Cronet。

其它还有一些配置类 API,如设置 User-Agent 之类的。

特别地,有个叫 setHostResolverRulesForTesting 的 API,我们可以通过它设置域名对应的 IP,达到 HTTPDNS 的效果。经我测试,可以在访问 https://httpbin.thellsapi.com/get 的应答里,可看到 X-Alicdn-Da-Via 头,如:"X-Alicdn-Da-Via": "183.57.82.166,150.138.126.56",表示 SNI 支持,其中的 IP 183.57.82.166 通过 Cronet.setHostResolverRulesForTesting("MAP httpbin.thellsapi.com 183.57.82.166") 设置(格式参考其单元测试的写法)。

总体上,Cronet 暴露的 API 较少,并通过 iOS 提供的接口做到业务方透明(不引入新的 API)。

相关文件:

/components/cronet/ios/Cronet.h
/ios/net/crn_http_protocol.h

Host Resolver相关代码阅读

跟着 setHostResolverRulesForTesting API 分析 HostResolver 的工作原理。

setHostResolverRulesForTesting 将调用 gChromeNet.Get() 的 SetHostResolverRules 方法,而 gChromeNet.Get() 是一个 CronetEnvironment 实例(而且目前是唯一的实例,会在关闭时泄漏)。

其中 gChromeNet 利用了 LAZY_INSTANCE_INITIALIZER 做初始化。

相关文件:

/components/cronet/ios/cronet_environment.h
/base/lazy_instance.h

CronetEnvironment

它包含网路栈相关的所有配置。例如上面通过 Cronet 设置的信息最终会传递到 CronetEnvironment 中,例如 User-Agent。

我们在 cronet_environment.mm 里看到 SetHostResolverRules 的实现,它在内部调用 SetHostResolverRulesOnNetworkThread,后者先将 main_context_->host_resolver() 转换为 MappedHostResolver,再调用其方法 SetRulesFromString。其中 maincontext 是一个 URLRequestContext,而 Host Resolver 是在 CronetEnvironment::InitializeOnNetworkThread 里使用 mapped_host_resolver 利用 context_builder.set_host_resolver 初始化的,context_builder 正是一个 URLRequestContextBuilder,它最后创建出 maincontext

奇怪的是 main_contextgetter 是一个 CronetURLRequestContextGetter(定义在 cronet_environment.mm 里,是 URLRequestContextGetter 的子类),还不清楚 main_contextgetter 的用途。它初始化时会传入 CronetEnvironment* environment,这里面有main_context_->host_resolver()。且其 GetURLRequestContext() 返回的就是 environment_→GetURLRequestContext(),它最终返回的是 maincontext,连起来的吧!

相关文件:

/components/cronet/ios/cronet_environment.h
/net/url_request/url_request_context.h
/net/url_request/url_request_context_builder.h
/net/dns/mapped_host_resolver.h

MappedHostResolver

MappedHostResolver 是 HostResolver 的子类。它包含一个 HostResolver 实例(通过初始化传递),它将在请求传递到 impl 前利用 rules 的 RewriteHost 修改之。其中 impl_ 就是内部的 HostResolver,rules 是一个 HostMappingRules。之前提到的 SetRulesFromString 就是调用 HostMappingRules 的 SetRulesFromString 方法。

CRNHTTPProtocolHandler

在 CRNHTTPProtocolHandler 内部,定义了 HttpProtocolHandlerCore。在拦截到请求后,它利用一个 NSURLRequest 生成 HttpProtocolHandlerCore 实例 _core,然后在 startLoading 里绑定调用 net::HttpProtocolHandlerCore::Start,也即 void HttpProtocolHandlerCore::Start(id<CRNNetworkClientProtocol> base_client),处理一番后,调用 net_request_->Start(),其中 netrequest 由 context 创建 net_request_ = context->CreateRequest(url, DEFAULT_PRIORITY, this).release(),而这里的 context 就是一个 URLRequestContext,由 g_protocol_handler_delegate 生成。

g_protocol_handler_delegate 是 HTTPProtocolHandlerDelegate 类型,是个全局静态变量,由 Cronet 在初始化的时候在 startInternal 里设置。

net::HTTPProtocolHandlerDelegate::SetInstance( gHttpProtocolHandlerDelegate.Get().get());

而 gHttpProtocolHandlerDelegate 是一个 CronetHttpProtocolHandlerDelegate 的 lazy 实例。所以,最终 g_protocol_handler_delegate 就是一个 CronetHttpProtocolHandlerDelegate。

HTTPProtocolHandlerDelegate

它定义在 crn_http_protocol_hander.h 里,不过其方法都是 virtual 标记的,这也符合上面的分析。

CronetHttpProtocolHandlerDelegate

它定义在 Cronet.mm 里,它具体实现了 CanHandleRequest、IsRequestSupported 和 GetDefaultURLRequestContext。

其中 GetDefaultURLRequestContext 返回的是一个 URLRequestContextGetter,即它内部的 getter_,其在初始化时传入,就是 gChromeNet.Get()→GetURLRequestContextGetter(),最终返回的是 CronetEnvironment 里的 main_contextgetter,终于找到它的用途了。

可见,对于 iOS 上的实现来说,其作用的是 main_contextgetter 针对每个请求构造 URLRequestContext,然后创建 request 再 Start()

相关文件:

/ios/net/crn_http_protocol_hander.h
/components/cronet/ios/Cronet.mm

HostResolver 如何被使用?

在 CronetURLRequestContext::NetworkTasks::Initialize 中,

// Set up host cache persistence if it's enabled. Happens after building the
// URLRequestContext to get access to the HostCache.
if (config->enable_host_cache_persistence && cronet_prefs_manager_) {
    net::HostCache* host_cache = context_->host_resolver()->GetHostCache();
    cronet_prefs_manager_->SetupHostCachePersistence(
    host_cache, config->host_cache_persistence_delay_ms,
    g_net_log.Get().net_log());
}

URLRequest

之前生成 request 时,使用了 HttpProtocolHandlerCore 的 context 作为 delegate

在 其Start() 中,利用 URLRequestJobManager 构造 Job,使用 networkdelegate(NetworkDelegate),它使用初始化传入的 networkdelegate,最终来源是 URLRequestContext

context_builder.set_network_delegate( std::make_unique<BasicNetworkDelegate>());

相关文件:

/net/url_request.h
octsama commented 2 years ago

如果按照你的逻辑,HostResolver不是在这里被使用的吧,看代码这里注释很清楚啊。 // Set up host cache persistence if it's enabled. Happens after building the // URLRequestContext to get access to the HostCache.

应该是HttpStreamFactory::JobController::DoLoop这里的RewriteUrlWithHostMappingRules(origin_url)