Chromium net 的Cronet如何加入HostResolver

背景

这个是源于项目中为了减少DNS的解析时间,同时又能够使用Chromium的net库。

源码分析

Cronet本身是编译了net库,同时提供了一个已经封装好的Java Api,不得不说Google考虑还是很周到的,但Cronet本身提供的Api接口比较少,通过json的配置又不能动态的去更新,所以就需要在dns解析的时候来获取应用层的已经解析的dns列表。

所以,通过java的源码,CronetUrlRequestContext.Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@UsedByReflection("CronetEngine.java")
public CronetUrlRequestContext(final CronetEngineBuilderImpl builder) {
mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled();
CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);
if (!IntegratedModeState.INTEGRATED_MODE_ENABLED) {
nativeSetMinLogLevel(getLoggingLevel());
}
if (builder.httpCacheMode() == HttpCacheType.DISK) {
mInUseStoragePath = builder.storagePath();
synchronized (sInUseStoragePaths) {
if (!sInUseStoragePaths.add(mInUseStoragePath)) {
throw new IllegalStateException("Disk cache storage path already in use");
}
}
} else {
mInUseStoragePath = null;
}
synchronized (mLock) {
mUrlRequestContextAdapter =
nativeCreateRequestContextAdapter(createNativeUrlRequestContextConfig(builder));
if (mUrlRequestContextAdapter == 0) {
throw new NullPointerException("Context Adapter creation failed.");
}
}

// Init native Chromium URLRequestContext on init thread.
CronetLibraryLoader.postToInitThread(new Runnable() {
@Override
public void run() {
CronetLibraryLoader.ensureInitializedOnInitThread();
synchronized (mLock) {
// mUrlRequestContextAdapter is guaranteed to exist until
// initialization on init and network threads completes and
// initNetworkThread is called back on network thread.
nativeInitRequestContextOnInitThread(mUrlRequestContextAdapter);
}
}
});
}

Api层通过CronetEngineBuilder 来构造所提供的参数,然后设置到Jni层。

1
CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);

这个先确定库的初始化。 然后在create native 的Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
synchronized (mLock) {
mUrlRequestContextAdapter =
nativeCreateRequestContextAdapter(createNativeUrlRequestContextConfig(builder));
if (mUrlRequestContextAdapter == 0) {
throw new NullPointerException("Context Adapter creation failed.");
}
}

@VisibleForTesting
public static long createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder) {
final long urlRequestContextConfig = nativeCreateRequestContextConfig(
builder.getUserAgent(), builder.storagePath(), builder.quicEnabled(),
builder.getDefaultQuicUserAgentId(), builder.http2Enabled(),
builder.brotliEnabled(), builder.cacheDisabled(), builder.httpCacheMode(),
builder.httpCacheMaxSize(), builder.experimentalOptions(),
builder.mockCertVerifier(), builder.networkQualityEstimatorEnabled(),
builder.publicKeyPinningBypassForLocalTrustAnchorsEnabled(),
builder.threadPriority(Process.THREAD_PRIORITY_BACKGROUND));
for (CronetEngineBuilderImpl.QuicHint quicHint : builder.quicHints()) {
nativeAddQuicHint(urlRequestContextConfig, quicHint.mHost, quicHint.mPort,
quicHint.mAlternatePort);
}
for (CronetEngineBuilderImpl.Pkp pkp : builder.publicKeyPins()) {
nativeAddPkp(urlRequestContextConfig, pkp.mHost, pkp.mHashes, pkp.mIncludeSubdomains,
pkp.mExpirationDate.getTime());
}
return urlRequestContextConfig;
}

优先是先创建 nativeCreateRequestContextConfig 创建配置,我们就可以延着这个native
调用栈来查看native处理流程,SDK有一行注释代码

1
2
3
4
5
6
7
// Native methods are implemented in cronet_url_request_context_adapter.cc.
private static native long nativeCreateRequestContextConfig(String userAgent,
String storagePath, boolean quicEnabled, String quicUserAgentId, boolean http2Enabled,
boolean brotliEnabled, boolean disableCache, int httpCacheMode, long httpCacheMaxSize,
String experimentalOptions, long mockCertVerifier,
boolean enableNetworkQualityEstimator,
boolean bypassPublicKeyPinningForLocalTrustAnchors, int networkThreadPriority);

cronet_url_request_context_adapter.cc 就是入口了,我们查看下这个类,找到如下函数:

1
// Create a URLRequestContextConfig from the given parameters.
static jlong JNI_CronetUrlRequestContext_CreateRequestContextConfig(
    JNIEnv* env,
    const JavaParamRef<jstring>& juser_agent,
    const JavaParamRef<jstring>& jstorage_path,
    jboolean jquic_enabled,
    const JavaParamRef<jstring>& jquic_default_user_agent_id,
    jboolean jhttp2_enabled,
    jboolean jbrotli_enabled,
    jboolean jdisable_cache,
    jint jhttp_cache_mode,
    jlong jhttp_cache_max_size,
    const JavaParamRef<jstring>& jexperimental_quic_connection_options,
    jlong jmock_cert_verifier,
    jboolean jenable_network_quality_estimator,
    jboolean jbypass_public_key_pinning_for_local_trust_anchors,
    jint jnetwork_thread_priority) {
  return reinterpret_cast<jlong>(new URLRequestContextConfig(
      jquic_enabled,
      ConvertNullableJavaStringToUTF8(env, jquic_default_user_agent_id),
      jhttp2_enabled, jbrotli_enabled,
      static_cast<URLRequestContextConfig::HttpCacheType>(jhttp_cache_mode),
      jhttp_cache_max_size, jdisable_cache,
      ConvertNullableJavaStringToUTF8(env, jstorage_path),
      /* accept_languages */ std::string(),
      ConvertNullableJavaStringToUTF8(env, juser_agent),
      ConvertNullableJavaStringToUTF8(env,
                                      jexperimental_quic_connection_options),
      base::WrapUnique(
          reinterpret_cast<net::CertVerifier*>(jmock_cert_verifier)),
      jenable_network_quality_estimator,
      jbypass_public_key_pinning_for_local_trust_anchors,
      jnetwork_thread_priority >= -20 && jnetwork_thread_priority <= 19
          ? base::Optional<double>(jnetwork_thread_priority)
          : base::Optional<double>()));
}

这个是一个静态的函数,把Java端的参数传递到到URLRequestContextConfig这个类里面。 在Java层我们注意到这行代码:

1
nativeCreateRequestContextAdapter(createNativeUrlRequestContextConfig(builder));

也就是说创建的配置项会传递给创建出来的CreateRequestContextAdapter类,我们看看jni的CreateRequestContextAdapter类的构造函数:

1
CronetURLRequestContextAdapter::CronetURLRequestContextAdapter(
    std::unique_ptr<URLRequestContextConfig> context_config) {
  // Create context and pass ownership of |this| (self) to the context.
  std::unique_ptr<CronetURLRequestContextAdapter> self(this);
#if BUILDFLAG(INTEGRATED_MODE)
  // Create CronetURLRequestContext running in integrated network task runner.
  context_ =
      new CronetURLRequestContext(std::move(context_config), std::move(self),
                                  GetIntegratedModeNetworkTaskRunner());
#else
  context_ =
      new CronetURLRequestContext(std::move(context_config), std::move(self));
#endif
}

从CronetURLRequestContextAdapter的构造函数中我们能过看到参数是URLRequestContextConfig,通过参数创建出CronetURLRequestContext,然后通过context_的上下文来进行Start等操作,所以进行参数解析的是在CronetURLRequestContext中,我们就看看CronetURLRequestContext做了什么事情,入口是构造函数:

1
CronetURLRequestContext::CronetURLRequestContext(
    std::unique_ptr<URLRequestContextConfig> context_config,
    std::unique_ptr<Callback> callback,
    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner)
    : default_load_flags_(
          net::LOAD_NORMAL |
          (context_config->load_disable_cache ? net::LOAD_DISABLE_CACHE : 0)),
      network_tasks_(
          new NetworkTasks(std::move(context_config), std::move(callback))),
      network_task_runner_(network_task_runner) {
  if (!network_task_runner_) {
    network_thread_ = std::make_unique<base::Thread>("network");
    base::Thread::Options options;
    options.message_loop_type = base::MessageLoop::TYPE_IO;
    network_thread_->StartWithOptions(options);
    network_task_runner_ = network_thread_->task_runner();
  }
}

context_config 作为NetworkTasks的参数,我们看看NetworkTasks,不难发现NetworkTasks中正是进行参数转换的地方

1
void CronetURLRequestContext::NetworkTasks::Initialize(
    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
    scoped_refptr<base::SequencedTaskRunner> file_task_runner,
    std::unique_ptr<net::ProxyConfigService> proxy_config_service) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  DCHECK(!is_context_initialized_);

  std::unique_ptr<URLRequestContextConfig> config(std::move(context_config_));
  network_task_runner_ = network_task_runner;
  if (config->network_thread_priority)
    SetNetworkThreadPriorityOnNetworkThread(
        config->network_thread_priority.value());
  base::DisallowBlocking();
  net::URLRequestContextBuilder context_builder;
  context_builder.set_network_delegate(
      std::make_unique<BasicNetworkDelegate>());
  context_builder.set_net_log(g_net_log.Get().net_log());

  context_builder.set_proxy_resolution_service(
      cronet::CreateProxyResolutionService(std::move(proxy_config_service),
                                           g_net_log.Get().net_log()));

  config->ConfigureURLRequestContextBuilder(&context_builder,
                                            g_net_log.Get().net_log());                                            

通过config来构建出 net::URLRequestContextBuilder,我们看看net::URLRequestContextBuilder的组成,其中有一行:

1
// By default host_resolver is constructed with CreateDefaultResolver.
  void set_host_resolver(std::unique_ptr<HostResolver> host_resolver);

到这里我们就知道,其实CronetURLRequestContext 通过URLRequestContextConfig 来构建自己的URLRequestContextBuilder,URLRequestContextBuilder中包含了设置的所有参数。

在回到我们的问题,我们是要解决自定义dns解析,而Java层没有接口来提供可选的设置项。在Java层可以通过设置ExperimentalOptions来配置不同的参数来开启功能,但这个一旦设置了不能动态更改。在url_request_context_config.cc 的 ParseAndSetExperimentalOptions 中有一段代码如下:

1
if (async_dns_enable || stale_dns_enable || host_resolver_rules_enable ||
      disable_ipv6_on_wifi) {
    CHECK(net_log) << "All DNS-related experiments require NetLog.";
    std::unique_ptr<net::HostResolver> host_resolver;
    if (stale_dns_enable) {
      DCHECK(!disable_ipv6_on_wifi);
      host_resolver.reset(new StaleHostResolver(
          net::HostResolver::CreateDefaultResolverImpl(net_log),
          stale_dns_options));
    } else {
      host_resolver = net::HostResolver::CreateDefaultResolver(net_log);
    }
    if (disable_ipv6_on_wifi)
      host_resolver->SetNoIPv6OnWifi(true);
    if (async_dns_enable)
      host_resolver->SetDnsClientEnabled(true);
    if (host_resolver_rules_enable) {
      std::unique_ptr<net::MappedHostResolver> remapped_resolver(
          new net::MappedHostResolver(std::move(host_resolver)));
      remapped_resolver->SetRulesFromString(host_resolver_rules_string);
      host_resolver = std::move(remapped_resolver);
    }
    context_builder->set_host_resolver(std::move(host_resolver));
  }

以上代码的作用就是在开启不同的值时可以提供域名对应的值,但这个只是局限于首次加载配置选项,后面无法更改。

HostResolver域名解析

HostResolver是域名解析的服务的接口,我们先看看这个接口做了什么事情。

1
virtual int Resolve(const RequestInfo& info,
                      RequestPriority priority,
                      AddressList* addresses,
                      CompletionOnceCallback callback,
                      std::unique_ptr<Request>* out_req,
                      const NetLogWithSource& net_log) = 0;                                      

这个方法的目的是将解析的ip存储到addresses中,所以,我们就能过在这个地方加入Java端的dns解析