從Cronet 看Http3和QUIC(一)

語言: CN / TW / HK

前言

前一段時間,在公司內部進行了一次QUIC協議的演講。當時因為時間有限,沒有仔細的討論Cronet 的原始碼細節,僅僅只是介紹了QUIC的協議細節。本文就從Cronet原始碼出發,聊聊QUIC的一些實現,進而看看QUIC對比Http2的優勢,解決了什麼問題?

網上搜了不少QUIC解析文章,不是太老就是粗略聊聊原理,沒有幾個真的深入原始碼層面來證明說法是否正確,本文將主要根據QUIC最新的原始碼來聊聊整個協議的設計,以及這樣做的優勢

正文

Http 發展史

在聊QUIC之前,我們需要對QUIC有一個初步的瞭解。QUIC本質上是在Http之上進一步的發展,而不是憑空出現的,而是為了解決Http之前的痛點誕生的。為此我們需要先了解http的發展,以及每一代比起上一代都解決了什麼問題。

下圖是一個Http的發展程序:

Http發展.png

在這個發展史中可以看到在Http2.0正式推出之前,Google就開始實驗QUIC協議。並在2018年在QUIC基礎上進一步的發展出Http3協議。在2021年正式釋出出QUIC協議。

能看到Http 1到SPDY協議中間間隔10年時間,究竟是為什麼在Http 2.0正式釋出之前,就開始了Http3前身QUIC進行實驗了?那必然是很早就被Google發現了Http 2.0協議的有根本性的缺陷,無法被彌補,需要立即實驗下一代協議。

再來看看如今Http 2.0的全網使用情況:

Http2 全網佔比.png

能看到目前Http 2.0全網使用率從發展到2022年2月份還是50%的使用率,而在5月份就是驟降了5%。實際上都轉去了QUIC協議,如今已盡佔比接近25%了。那究竟有什麼魅力,導致這麼多開發者青睞QUIC協議呢?

帶著疑問,我們來簡單回顧一下每一代Http協議實現的功能,以及缺點。

Http 1

在Http 1中奠定了Http協議的基本語義:

  • 由請求行/狀態行,body和header 構成 Http請求

Http請求協議結構.png

Http響應協議結構.png

Http的缺點分為如下幾點:

  • 1.header 編碼效率低:特別是Rest 架構,往往無狀態容易,沒有對Header進行編碼壓縮

  • 2.多路複用成本過高

  • 慢啟動
  • 一旦網路發生異動就需要重建連線,無論如何都需要3次握手,緩慢

  • 3.一旦長連線建立了就無法中斷

  • 4.Http 應用層不支援流控

為了解決這些問題,就誕生出了Http 2.0協議。

Http 2.0

Http 2.0在Http 1.0基礎上實現瞭如下的功能:

  • 1.多路複用
  • 連線,Stream級別流控
  • 帶權重,依賴優先順序
  • Stream Reset
  • 應用層資料交換單位細化到Frame(幀)

  • 2.HPack 頭部編碼

  • 3.伺服器訊息推送

關於Http 2.0詳細的設計,可以閱讀我之前寫的Okhttp原始碼解析中的Http2Connection相關的原始碼解析。裡面有詳細剖析Okhttp是如何進行Frame江湖,以及HPack是如何壓縮的,還有流是如何控制的。

Http 2.0的缺點如下:

  • 1.隊頭阻塞

網路協議-Http2隊頭阻塞.png

因為Http 2.0中使用的是多路複用的流模型,一個tcp連結的傳送資料過程中可能會把一個個請求分割成多個流傳送到伺服器。,因為Tcp的tls加密是一個Record的加密,也就是接近10stream大小進行加密。如果其中在某一個流丟失了,整一串都會解密失敗。

這就是Http 2.0最為嚴重的隊頭阻塞問題。

  • 2.建立連線速度緩慢,能看到整個過程都需要一個十分冗長的過程,三次握手,tls金鑰交換等等。可以簡單看看https的建立連結過程:

Https通訊模型.png

  • 3.基於TCP四元組確定一個連結,在移動網際網路中表現不佳。因為移動裝置經常移動,可能在公交地鐵等地方,出現了基站變換,Wi-Fi變化等狀態。導致四元組發聲變化,而需要重新建立連結。

QUIC

Http 2.0的問題很大情況是因為TCP本身在傳輸層本身就需要保證包的有序性導致的,因此QUIC乾脆拋棄TCP協議,使用UDP協議,可以看看下面QUIC的協議構成:

網路協議-QUIC_Http3.png

Http2是基於TCP協議,並在可以獨立出tls加密協議出來。可以選擇是否使用tls加密。

能看到QUIC協議本質上是基於UDP傳輸層協議。在這個之上的應用層是QUIC協議,其中包含了tls加密協議。而未來的Http3則是在QUIC協議上進一步發展的。

QUIC的原始碼十分之多和複雜?為什麼如此呢?能看到QUIC實際上是在UDP上發展,那麼需要保證網路資料包的有序性以及正確性,就需要把類似TCP可靠協議邏輯放在QUIC中實現。

也正因如此,QUIC是在應用層實現的協議,可以很靈活的切換各種協議狀態,而不需要在核心中增加socket中netFamily的族群,在傳輸層增加邏輯。

為什麼QUIC協議中內建tls協議呢?往後看就知道優勢在哪裡了。

QUIC 使用

在聊QUIC之前,我們需要熟悉這個協議cronet是如何使用的QUIC協議的。

估計熟悉的人不多,因為cronet網路庫官方告訴你的依賴方式,需要的引擎需要通過GooglePlay獲取到cronet的引擎才能完整的使用所有的功能。國內環境一般是沒有GooglePlay因此如果想要使用cronet,最好把原始碼弄下來,自己生成so庫或者使用生成好的so庫。

這裡就不多說依賴,來看看如何使用的:

  • 1.先生成一個CronetEngine引擎

java CronetEngine.Builder myBuilder = new CronetEngine.Builder(context); CronetEngine cronetEngine = myBuilder.build();

  • 2.構造一個網路請求過程中,不同狀態的回撥函式:

```java class MyUrlRequestCallback extends UrlRequest.Callback { private static final String TAG = "MyUrlRequestCallback";

  @Override
  public void onRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
    Log.i(TAG, "onRedirectReceived method called.");
    // You should call the request.followRedirect() method to continue
    // processing the request.
    request.followRedirect();
  }

  @Override
  public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
    Log.i(TAG, "onResponseStarted method called.");
    // You should call the request.read() method before the request can be
    // further processed. The following instruction provides a ByteBuffer object
    // with a capacity of 102400 bytes to the read() method.
    request.read(ByteBuffer.allocateDirect(102400));
  }

  @Override
  public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
    Log.i(TAG, "onReadCompleted method called.");
    // You should keep reading the request until there's no more data.
    request.read(ByteBuffer.allocateDirect(102400));
  }

  @Override
  public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
    Log.i(TAG, "onSucceeded method called.");
  }
}

```

    1. 生成UrlRequest物件,並啟動請求:

```java Executor executor = Executors.newSingleThreadExecutor(); UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder( "https://www.example.com", new MyUrlRequestCallback(), executor);

UrlRequest request = requestBuilder.build();
request.start();

```

其實就是這麼簡單。

下面是對Cronet中UrlRequest請求的生命週期:

cronet-lifecycle.png

QUIC 原始碼架構

在聊QUIC原始碼之前,我們需要初步的對Cronet的原始碼架構有一個瞭解。這部分的原始碼實在太多,接下來的原始碼使用的分支是最新的chromium 瀏覽器核心中Cronet模組。

下面是一個Cronet的核心類在整個Cronet的組成:

cronet.png

根據上面的示意圖,可以看到Cronet,將整個模組分為如下幾個部分:

  • 面向應用的api層,如Android,iOS。
  • iOS 則是由一個Cronet的中類方法通過cronet_environment控制cronet引擎。
  • Android 則複雜很多。首先面向開發者的java-api介面,在這個api介面中有4種不同的實現,分別是GmsCoreCronetProvider,PlayServicesCronetProviderNativeCronetProvider,JavaCronetProvider.為什麼會這樣呢?其實前兩者是在Google環境和存在Google商店下內建了Cronet的元件庫,那麼就可以直接複用Google為你提供的Cronet 網路請求服務,從而減小包大小。當然如果上述Cronet引擎都找不到,就會裝載預設的JavaCronetProvider物件,通過JavaUrlRequest使用URLConnection進行網路請求。當然我們可以把GmsCoreCronetProvider,PlayServicesCronetProvider看成NativeCronetProvider也未嘗不可,之後我們也只看這個引擎載入器的原始碼。最終NativeCronetProvider 最終會生成CronetUrlRequest`物件交給開發者進行請求

  • 對於Android jni層來說,幾乎java每一個步驟下生成的Java物件都會在jni的中有一個native物件。這裡只說核心的幾個。而在jni中,名字帶有Adapter的物件一般都是介面卡,連線java層對應在native的物件。

  • CronetURLRequest對應在jni中也有一個CronetURLRequest負責請求的總啟動.

  • CronetURLRequestAdapter 負責監聽CronetURLRequest回撥到java層對應的生命週期回撥。
  • CronetUploadDataStream 控制post時候需要傳送的訊息體資料流

  • Cronet Core 也就是cronet的核心引擎層。在這個層級裡面,無論是iOS還是Android最終都會呼叫到他們的api。其中在這個引擎層中包含了3部分,UrlRequest 控制請求事務流轉曾層快取與請求流控制層QUIC實現層。當然這cronet並不只是包含了qui協議c,同級別的具體協議實現還包含了如http1.0,http2.0,webscoket等,可以在UrlRequest組建的時候決定當前請求的協議應該是什麼。

  • URLRequest 所有的請求都會存在一個URLRequestJobFactory 請求工作工廠,當 URLRequest 需要執行的請求的時候,就會通過這個工廠生成一個Job物件進行生命週期的流轉,當真正執行的時候就會把事情委託給HttpCache,進行流級別的控制管理

  • HttpCache 本質上是一個面向流的快取。可以快取多個請求事務(Transaction),同時每個事務會控制不同的流。而每一個新的流都會生成一個全新的HttpStreamRequest,通過JobController 建立一個HttpStreamFactory::Job將請求委託給事務HttpTransactionHttpTransaction進行生命週期的流轉。而在這個全新的Job,就會根據之前在 URLRequest 配置好的協議頭,執行不同的協議請求處理器,並呼叫HttpTransaction開始請求。

  • QUIC協議層部分則是對應quic的具體實現。其中會生成一個QuicStreamRequest控制每一個quic請求流,而這個請求流會把事務委託給QuicStreamFactory生成QuicStreamFactory::Job工作物件。在Job中事務流轉整個請求的狀態。並在這個Job中控制UDP傳輸quic協議格式的資料。

有一個大致的認識後,讓我們進一步的瞭解整個QUIC的執行機制,再來看看QUIC協議中原理以及其優越性。

QUIC 原始碼解析

先根據使用來看看最初幾個api的設計,來看看CronetEngine.Builder的建構函式:

java public Builder(Context context) { this(createBuilderDelegate(context)); }

java private static ICronetEngineBuilder createBuilderDelegate(Context context) { List<CronetProvider> providers = new ArrayList<>(CronetProvider.getAllProviders(context)); CronetProvider provider = getEnabledCronetProviders(context, providers).get(0); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, String.format("Using '%s' provider for creating CronetEngine.Builder.", provider)); } return provider.createBuilder().mBuilderDelegate; }

能看到實際上是通過CronetProvider.getAllProviders獲取所有的Cronet引擎提供容器,通過getEnabledCronetProviders篩選出第一個可用的Cronet引擎。

CronetProvider getAllProviders

```java private static final String JAVA_CRONET_PROVIDER_CLASS = "org.chromium.net.impl.JavaCronetProvider";

private static final String NATIVE_CRONET_PROVIDER_CLASS =
        "org.chromium.net.impl.NativeCronetProvider";

private static final String PLAY_SERVICES_CRONET_PROVIDER_CLASS =
        "com.google.android.gms.net.PlayServicesCronetProvider";

private static final String GMS_CORE_CRONET_PROVIDER_CLASS =
        "com.google.android.gms.net.GmsCoreCronetProvider";

... private static final String RES_KEY_CRONET_IMPL_CLASS = "CronetProviderClassName";

public static List<CronetProvider> getAllProviders(Context context) {
    // Use LinkedHashSet to preserve the order and eliminate duplicate providers.
    Set<CronetProvider> providers = new LinkedHashSet<>();
    addCronetProviderFromResourceFile(context, providers);
    addCronetProviderImplByClassName(
            context, PLAY_SERVICES_CRONET_PROVIDER_CLASS, providers, false);
    addCronetProviderImplByClassName(context, GMS_CORE_CRONET_PROVIDER_CLASS, providers, false);
    addCronetProviderImplByClassName(context, NATIVE_CRONET_PROVIDER_CLASS, providers, false);
    addCronetProviderImplByClassName(context, JAVA_CRONET_PROVIDER_CLASS, providers, false);
    return Collections.unmodifiableList(new ArrayList<>(providers));
}

private static boolean addCronetProviderImplByClassName(
        Context context, String className, Set<CronetProvider> providers, boolean logError) {
    ClassLoader loader = context.getClassLoader();
    try {
        Class<? extends CronetProvider> providerClass =
                loader.loadClass(className).asSubclass(CronetProvider.class);
        Constructor<? extends CronetProvider> ctor =
                providerClass.getConstructor(Context.class);
        providers.add(ctor.newInstance(context));
        return true;
    } catch (InstantiationException e) {
        logReflectiveOperationException(className, logError, e);
    } catch (InvocationTargetException e) {
        logReflectiveOperationException(className, logError, e);
    } catch (NoSuchMethodException e) {
        logReflectiveOperationException(className, logError, e);
    } catch (IllegalAccessException e) {
        logReflectiveOperationException(className, logError, e);
    } catch (ClassNotFoundException e) {
        logReflectiveOperationException(className, logError, e);
    }
    return false;
}

private static boolean addCronetProviderFromResourceFile(
        Context context, Set<CronetProvider> providers) {
    int resId = context.getResources().getIdentifier(
            RES_KEY_CRONET_IMPL_CLASS, "string", context.getPackageName());
    // Resource not found
    if (resId == 0) {
        // The resource wasn't included in the app; therefore, there is nothing to add.
        return false;
    }
    String className = context.getResources().getString(resId);

    if (className == null || className.equals(PLAY_SERVICES_CRONET_PROVIDER_CLASS)
            || className.equals(GMS_CORE_CRONET_PROVIDER_CLASS)
            || className.equals(JAVA_CRONET_PROVIDER_CLASS)
            || className.equals(NATIVE_CRONET_PROVIDER_CLASS)) {
        return false;
    }

    if (!addCronetProviderImplByClassName(context, className, providers, true)) {

... } return true; } ```

能看到整個核心就是 - 1.獲取資源ID為CronetProviderClassName 所對應的Cronet引擎類名。 - 2.反射內建好的GmsCoreCronetProvider,PlayServicesCronetProviderNativeCronetProvider,JavaCronetProvider四種類名為預設引擎提供器

在這裡我們只需要閱讀NativeCronetProvider#createBuilder().mBuilderDelegate相關的原始碼即可。

NativeCronetProvider createBuilder

```java public class NativeCronetProvider extends CronetProvider {

@UsedByReflection("CronetProvider.java")
public NativeCronetProvider(Context context) {
    super(context);
}

@Override
public CronetEngine.Builder createBuilder() {
    ICronetEngineBuilder impl = new NativeCronetEngineBuilderWithLibraryLoaderImpl(mContext);
    return new ExperimentalCronetEngine.Builder(impl);
}

`` 從名字能很侵襲的看到整個過程是一個委託者設計模式,構建一個ExperimentalCronetEngine物件,而這個物件將真正的執行者委託給NativeCronetEngineBuilderWithLibraryLoaderImpl.而之前的mBuilderDelegate 就是指NativeCronetEngineBuilderWithLibraryLoaderImpl`物件。

NativeCronetEngineBuilderWithLibraryLoaderImpl build

```java public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl {

public NativeCronetEngineBuilderImpl(Context context) {
    super(context);
}

@Override
public ExperimentalCronetEngine build() {
    if (getUserAgent() == null) {
        setUserAgent(getDefaultUserAgent());
    }

    ExperimentalCronetEngine builder = new CronetUrlRequestContext(this);

    // Clear MOCK_CERT_VERIFIER reference if there is any, since
    // the ownership has been transferred to the engine.
    mMockCertVerifier = 0;

    return builder;
}

} ```

能看到直接返回CronetUrlRequestContext物件作為CronetEngine返回給應用層。之後會通過CronetUrlRequestContext呼叫newUrlRequestBuilder獲取UrlRequestBuilder。

java public CronetEngineBuilderImpl(Context context) { mApplicationContext = context.getApplicationContext(); enableQuic(true); enableHttp2(true); enableBrotli(false); enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISABLED, 0); enableNetworkQualityEstimator(false); enablePublicKeyPinningBypassForLocalTrustAnchors(true); }

CronetEngineBuilderImpl將預設支援quic的選項,http2選項,關閉httpCache。

CronetUrlRequestContext 建構函式

```java public CronetUrlRequestContext(final CronetEngineBuilderImpl builder) { mRttListenerList.disableThreadAsserts(); mThroughputListenerList.disableThreadAsserts(); mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled(); CronetLibraryLoader.ensureInitialized(builder.getContext(), builder); if (!IntegratedModeState.INTEGRATED_MODE_ENABLED) { CronetUrlRequestContextJni.get().setMinLogLevel(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 = CronetUrlRequestContextJni.get().createRequestContextAdapter( 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.
                CronetUrlRequestContextJni.get().initRequestContextOnInitThread(
                        mUrlRequestContextAdapter, CronetUrlRequestContext.this);
            }
        }
    });
}

```

這裡核心就是圍繞3個native方法:

  • 1.CronetUrlRequestContextJni.get().setMinLogLevel(getLoggingLevel()) 設定日誌等級
  • 2.CronetUrlRequestContextJni.get().createRequestContextAdapter 通過createNativeUrlRequestContextConfig獲取當前Cronet的配置在native下層建立一個UrlRequestContextAdapter
  • 3.CronetUrlRequestContextJni.get().initRequestContextOnInitThread在非同步執行緒中初始化。

核心是CronetUrlRequestContextJni.get().createRequestContextAdapter 以及CronetUrlRequestContextJni.get().initRequestContextOnInitThread。要弄懂Cronet在jni層呼叫之前需要了解Cronet的jni初始化的JNI_OnLoad 做了什麼。

不過在這之前先來簡單看看createNativeUrlRequestContextConfig看看UrlRequestContextConfig(UrlRequest上下文的配置)都有些什麼選項?

createNativeUrlRequestContextConfig 建立Context配置物件

java public static long createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder) { final long urlRequestContextConfig = CronetUrlRequestContextJni.get().createRequestContextConfig(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)); if (urlRequestContextConfig == 0) { throw new IllegalArgumentException("Experimental options parsing failed."); } for (CronetEngineBuilderImpl.QuicHint quicHint : builder.quicHints()) { CronetUrlRequestContextJni.get().addQuicHint(urlRequestContextConfig, quicHint.mHost, quicHint.mPort, quicHint.mAlternatePort); } for (CronetEngineBuilderImpl.Pkp pkp : builder.publicKeyPins()) { CronetUrlRequestContextJni.get().addPkp(urlRequestContextConfig, pkp.mHost, pkp.mHashes, pkp.mIncludeSubdomains, pkp.mExpirationDate.getTime()); } return urlRequestContextConfig; }

能看到配置除了上面說過的quic模式和,http2模式。還有httpCache的開關以及Cache的大小。

注意如果想要使用QUIC需要設定QuicHint,告訴QUIC協議哪些urlhost支援quic協議。

另外,還能通過設定CronetEngineBuilderImpl.Pkp 設定預設的加密公鑰。

cronet_jni JNI_OnLoad

```c extern "C" jint JNI_OnLoad(JavaVM vm, void reserved) { return cronet::CronetOnLoad(vm, reserved); }

extern "C" void JNI_OnUnLoad(JavaVM vm, void reserved) { cronet::CronetOnUnLoad(vm, reserved); } ```

```c jint CronetOnLoad(JavaVM vm, void reserved) { base::android::InitVM(vm); JNIEnv* env = base::android::AttachCurrentThread(); if (!RegisterMainDexNatives(env) || !RegisterNonMainDexNatives(env)) { return -1; } if (!base::android::OnJNIOnLoadInit()) return -1; NativeInit(); return JNI_VERSION_1_6; }

void CronetOnUnLoad(JavaVM jvm, void reserved) { if (base::ThreadPoolInstance::Get()) base::ThreadPoolInstance::Get()->Shutdown();

base::android::LibraryLoaderExitHook(); } ```

  • 1.RegisterMainDexNativesRegisterNonMainDexNatives 實際上是載入通過jni_registration_generator.py生成的cpp檔案。這種檔案生成出來就是為了減少dlsym()耗時。

瞭解jni的小夥伴都會清楚jni有兩種註冊方式一種是簡單的直接宣告native方法,然後通過AS可以自動生成包名_類名_方法名的cpp方法。而後虛擬機器載入native的方法時候,就會通過dlsym()呼叫查詢so動態庫中的對應的方法。另一種則是通過JNIEnv->RegisterNatives手動在JNI_OnLoad註冊當前的native方法關聯的java方法(注意要有指向包和類名)。

jni_registration_generator.py就是會遍歷所有java檔案中的native方法並JNIEnv->RegisterNatives手動註冊的程式碼cpp程式碼。同時會遍歷Java檔案中帶上了@CalledByNative方法,說明這是native想要呼叫java方法,也會生成相關的反射jmethod的方法的檔案。

在這裡RegisterMainDexNativesRegisterNonMainDexNatives 本質上就是裝在生成好的動態註冊的jni方法。

這個不是重點之後有機會再仔細聊聊。

    1. OnJNIOnLoadInit 這個方法就是獲取JNIUtils類中的ClassLoader,並獲取ClassLoader#loadClass的jmethodID儲存到全域性變數g_class_loader_load_class_method_id
  • 3.NativeInit 在全域性生成一個名為Cronet的執行緒池。

CreateRequestContextAdapter 初始化

``` cpp // Creates RequestContextAdater if config is valid URLRequestContextConfig, // returns 0 otherwise. static jlong JNI_CronetUrlRequestContext_CreateRequestContextAdapter( JNIEnv* env, jlong jconfig) { std::unique_ptr context_config( reinterpret_cast(jconfig));

CronetURLRequestContextAdapter* context_adapter = new CronetURLRequestContextAdapter(std::move(context_config)); return reinterpret_cast(context_adapter); } ```

很簡答就是初始化了CronetURLRequestContextAdapter一個cpp物件對應java物件,並返回當前物件的地址到java中。

CreateRequestContextAdapter 標頭檔案

要了解一個c++的類,首先看看標頭檔案,然後再看看建構函式。

```h namespace net { class NetLog; class URLRequestContext; } // namespace net

namespace cronet { class TestUtil;

struct URLRequestContextConfig;

// Adapter between Java CronetUrlRequestContext and CronetURLRequestContext. class CronetURLRequestContextAdapter : public CronetURLRequestContext::Callback { public: explicit CronetURLRequestContextAdapter( std::unique_ptr context_config);

CronetURLRequestContextAdapter(const CronetURLRequestContextAdapter&) = delete; CronetURLRequestContextAdapter& operator=( const CronetURLRequestContextAdapter&) = delete;

~CronetURLRequestContextAdapter() override;

... private: friend class TestUtil;

// Native Cronet URL Request Context. raw_ptr context_;

// Java object that owns this CronetURLRequestContextAdapter. base::android::ScopedJavaGlobalRef jcronet_url_request_context_; };

} // namespace cronet

endif // COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_CONTEXT_ADAPTER_H_

};

} // namespace cronet

```

在這裡其實持有了一個CronetURLRequestContextnative物件,這個物件顧名思義就是Cronet請求時候的上下文。同時持有將會持有一個UrlRequestContext的java物件。

之後當Cronet的狀態發生變化都會通過這裡的回撥java層的CronetUrlRequestContext中的監聽。

CronetURLRequestContextAdapter 建構函式

```cpp CronetURLRequestContextAdapter::CronetURLRequestContextAdapter( std::unique_ptr context_config) { // Create context and pass ownership of |this| (self) to the context. std::unique_ptr self(this);

if BUILDFLAG(INTEGRATED_MODE)

// Create CronetURLRequestContext running in integrated network task runner. ...

else

context_ = new CronetURLRequestContext(std::move(context_config), std::move(self));

endif

} ```

CronetURLRequestContextAdapter 則會建立一個CronetURLRequestContext物件

CronetURLRequestContext 標頭檔案

```h class CronetURLRequestContext { public: // Callback implemented by CronetURLRequestContext() caller and owned by // CronetURLRequestContext::NetworkTasks. class Callback { public: virtual ~Callback() = default;

// Invoked on network thread when initialized.
virtual void OnInitNetworkThread() = 0;

// Invoked on network thread immediately prior to destruction.
virtual void OnDestroyNetworkThread() = 0;

... };

CronetURLRequestContext( std::unique_ptr context_config, std::unique_ptr callback, scoped_refptr network_task_runner = nullptr);

CronetURLRequestContext(const CronetURLRequestContext&) = delete; CronetURLRequestContext& operator=(const CronetURLRequestContext&) = delete;

// Releases all resources for the request context and deletes the object. // Blocks until network thread is destroyed after running all pending tasks. virtual ~CronetURLRequestContext();

// Called on init thread to initialize URLRequestContext. void InitRequestContextOnInitThread(); ...

private: friend class TestUtil; class ContextGetter;

class NetworkTasks : public net::EffectiveConnectionTypeObserver, public net::RTTAndThroughputEstimatesObserver, public net::NetworkQualityEstimator::RTTObserver, public net::NetworkQualityEstimator::ThroughputObserver { public: // Invoked off the network thread. NetworkTasks(std::unique_ptr config, std::unique_ptr callback);

NetworkTasks(const NetworkTasks&) = delete;
NetworkTasks& operator=(const NetworkTasks&) = delete;

// Invoked on the network thread.
~NetworkTasks() override;

...

private: ... };

... };

} // namespace cronet

endif // COMPONENTS_CRONET_CRONET_URL_REQUEST_CONTEXT_H_

```

能看到這個標頭檔案分為3部分:

  • CronetURLRequestContext 承載所有URLRequest請求的上下文,主要用於構建網路任務的環境,回撥網路質量相關的回撥(如rtt耗時等)
  • Callback 用於回撥來自NetworkQualityEstimator物件對網路質量的監控
  • NetworkTasks 實現了Callback的回撥,承載網路請求的起始

CronetURLRequestContext 建構函式

```cpp

CronetURLRequestContext::CronetURLRequestContext( std::unique_ptr context_config, std::unique_ptr callback, scoped_refptr network_task_runner) : bidi_stream_detect_broken_connection_( context_config->bidi_stream_detect_broken_connection), heartbeat_interval_(context_config->heartbeat_interval), 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("network"); base::Thread::Options options; options.message_pump_type = base::MessagePumpType::IO; network_thread_->StartWithOptions(std::move(options)); network_task_runner_ = network_thread_->task_runner(); } } ```

初始化一個NetworkTasks作為一個網路請求任務承載者。並初始化一個名為network的執行緒,並以這個執行緒建立一個looper賦值給network_task_runner_,之後所有的請求任務都會在這個loop中開始。

initRequestContextOnInitThread

cpp void CronetURLRequestContext::InitRequestContextOnInitThread() { DCHECK(OnInitThread()); auto proxy_config_service = cronet::CreateProxyConfigService(GetNetworkTaskRunner()); g_net_log.Get().EnsureInitializedOnInitThread(); GetNetworkTaskRunner()->PostTask( FROM_HERE, base::BindOnce(&CronetURLRequestContext::NetworkTasks::Initialize, base::Unretained(network_tasks_), GetNetworkTaskRunner(), GetFileThread()->task_runner(), std::move(proxy_config_service))); } GetNetworkTaskRunner獲取network_task_runner_呼叫PostTask進入切換到network執行緒的loop,執行CronetURLRequestContext::NetworkTasks::Initialize方法

```cpp void CronetURLRequestContext::NetworkTasks::Initialize( scoped_refptr network_task_runner, scoped_refptr file_task_runner, std::unique_ptr proxy_config_service) {

std::unique_ptr 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()); 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); effective_experimental_options_ = base::Value(config->effective_experimental_options);

if (config->enable_network_quality_estimator) { std::unique_ptr nqe_params = std::make_unique( std::map()); if (config->nqe_forced_effective_connection_type) { nqe_params->SetForcedEffectiveConnectionType( config->nqe_forced_effective_connection_type.value()); }

network_quality_estimator_ = std::make_unique<net::NetworkQualityEstimator>(
    std::move(nqe_params), g_net_log.Get().net_log());
network_quality_estimator_->AddEffectiveConnectionTypeObserver(this);
network_quality_estimator_->AddRTTAndThroughputEstimatesObserver(this);

context_builder.set_network_quality_estimator(
    network_quality_estimator_.get());

}

...

// Disable net::CookieStore. context_builder.SetCookieStore(nullptr);

context_ = context_builder.Build();

..

if (config->enable_quic) { for (const auto& quic_hint : config->quic_hints) { if (quic_hint->host.empty()) { LOG(ERROR) << "Empty QUIC hint host: " << quic_hint->host; continue; }

  url::CanonHostInfo host_info;
  std::string canon_host(
      net::CanonicalizeHost(quic_hint->host, &host_info));
  if (!host_info.IsIPAddress() &&
      !net::IsCanonicalizedHostCompliant(canon_host)) {

... continue; }

  if (quic_hint->port <= std::numeric_limits<uint16_t>::min() ||
      quic_hint->port > std::numeric_limits<uint16_t>::max()) {

... continue; }

  if (quic_hint->alternate_port <= std::numeric_limits<uint16_t>::min() ||
      quic_hint->alternate_port > std::numeric_limits<uint16_t>::max()) {

... continue; }

  url::SchemeHostPort quic_server("https", canon_host, quic_hint->port);
  net::AlternativeService alternative_service(
      net::kProtoQUIC, "",
      static_cast<uint16_t>(quic_hint->alternate_port));
  context_->http_server_properties()->SetQuicAlternativeService(
      quic_server, net::NetworkIsolationKey(), alternative_service,
      base::Time::Max(), quic::ParsedQuicVersionVector());
}

}

for (const auto& pkp : config->pkp_list) { // Add the host pinning. context_->transport_security_state()->AddHPKP( pkp->host, pkp->expiration_date, pkp->include_subdomains, pkp->pin_hashes, GURL::EmptyGURL()); }

context_->transport_security_state() ->SetEnablePublicKeyPinningBypassForLocalTrustAnchors( config->bypass_public_key_pinning_for_local_trust_anchors);

callback_->OnInitNetworkThread(); is_context_initialized_ = true;

if (config->enable_network_quality_estimator && cronet_prefs_manager_) { network_task_runner_->PostTask( FROM_HERE, base::BindOnce( &CronetURLRequestContext::NetworkTasks::InitializeNQEPrefs, base::Unretained(this))); }

if BUILDFLAG(ENABLE_REPORTING)

if (context_->reporting_service()) { for (const auto& preloaded_header : config->preloaded_report_to_headers) { context_->reporting_service()->ProcessReportToHeader( preloaded_header.origin, net::NetworkIsolationKey(), preloaded_header.value); } }

if (context_->network_error_logging_service()) { for (const auto& preloaded_header : config->preloaded_nel_headers) { context_->network_error_logging_service()->OnHeader( net::NetworkIsolationKey(), preloaded_header.origin, net::IPAddress(), preloaded_header.value); } }

endif // BUILDFLAG(ENABLE_REPORTING)

while (!tasks_waiting_for_context_.empty()) { std::move(tasks_waiting_for_context_.front()).Run(); tasks_waiting_for_context_.pop(); } } ```

別看這段程式碼很長實際上做的事情也就如下幾件:

  • 1.根據URLRequestContextConfig裝載出NetworkQualityEstimator網路質量監控器
  • 2.建立URLRequestContext 物件,為之後的UrlRequest做準備
  • 3.如果URLRequestContextConfig允許了quic協議那麼會載入所有的QuicHint中的資源路徑,埠號作為識別。之後遇到這些請求就會使用quic協議,最後生成的SchemeHostPort通過SetQuicAlternativeService儲存到URLRequestContext

到目前為止java層的CronetUrlRequestContext通過native層的CronetUrlRequestContextAdapter建立了一個對應在native層的CronetUrlRequestContext物件進行一一對應。

當準備好了CronetUrlRequestContext,就可以使用CronetUrlRequestContext建立newUrlRequestBuilder請求

CronetEngineBase newUrlRequestBuilder 建立請求物件

java @Override public ExperimentalUrlRequest.Builder newUrlRequestBuilder( String url, UrlRequest.Callback callback, Executor executor) { return new UrlRequestBuilderImpl(url, callback, executor, this); }

UrlRequestBuilderImpl createRequest 建立請求物件

java @Override public UrlRequestBase createRequest(String url, UrlRequest.Callback callback, Executor executor, int priority, Collection<Object> requestAnnotations, boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener, int idempotency) { synchronized (mLock) { checkHaveAdapter(); return new CronetUrlRequest(this, url, priority, callback, executor, requestAnnotations, disableCache, disableConnectionMigration, allowDirectExecutor, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid, requestFinishedListener, idempotency); } }

很簡單,這裡把之前在UrlRequestBuilderImpl組合的引數都儲存到CronetUrlRequest返回給應用層。

CronetUrlRequest.start 啟動請求

```java @Override public void start() { synchronized (mUrlRequestAdapterLock) { checkNotStarted();

        try {
            mUrlRequestAdapter = CronetUrlRequestJni.get().createRequestAdapter(
                    CronetUrlRequest.this, mRequestContext.getUrlRequestContextAdapter(),
                    mInitialUrl, mPriority, mDisableCache, mDisableConnectionMigration,
                    mRequestContext.hasRequestFinishedListener()
                            || mRequestFinishedListener != null,
                    mTrafficStatsTagSet, mTrafficStatsTag, mTrafficStatsUidSet,
                    mTrafficStatsUid, mIdempotency);
            mRequestContext.onRequestStarted();
            if (mInitialMethod != null) {
                if (!CronetUrlRequestJni.get().setHttpMethod(
                            mUrlRequestAdapter, CronetUrlRequest.this, mInitialMethod)) {
                    throw new IllegalArgumentException("Invalid http method " + mInitialMethod);
                }
            }

            boolean hasContentType = false;
            for (Map.Entry<String, String> header : mRequestHeaders) {
                if (header.getKey().equalsIgnoreCase("Content-Type")
                        && !header.getValue().isEmpty()) {
                    hasContentType = true;
                }
                if (!CronetUrlRequestJni.get().addRequestHeader(mUrlRequestAdapter,
                            CronetUrlRequest.this, header.getKey(), header.getValue())) {
                    throw new IllegalArgumentException(
                            "Invalid header " + header.getKey() + "=" + header.getValue());
                }
            }
            if (mUploadDataStream != null) {
                if (!hasContentType) {
                    throw new IllegalArgumentException(
                            "Requests with upload data must have a Content-Type.");
                }
                mStarted = true;
                mUploadDataStream.postTaskToExecutor(new Runnable() {
                    @Override
                    public void run() {
                        mUploadDataStream.initializeWithRequest();
                        synchronized (mUrlRequestAdapterLock) {
                            if (isDoneLocked()) {
                                return;
                            }
                            mUploadDataStream.attachNativeAdapterToRequest(mUrlRequestAdapter);
                            startInternalLocked();
                        }
                    }
                });
                return;
            }
        } catch (RuntimeException e) {
            // If there's an exception, cleanup and then throw the exception to the caller.
            // start() is synchronized so we do not acquire mUrlRequestAdapterLock here.
            destroyRequestAdapterLocked(RequestFinishedInfo.FAILED);
            throw e;
        }
        mStarted = true;
        startInternalLocked();
    }
}

@GuardedBy("mUrlRequestAdapterLock")
private void startInternalLocked() {
    CronetUrlRequestJni.get().start(mUrlRequestAdapter, CronetUrlRequest.this);
}

```

這裡圍繞著4個核心的jni方法:

  • 1.createRequestAdapter 生成一個jni的UrlRequestAdapter物件
  • 2.回撥onRequestStarted生命週期
  • 3.setHttpMethod 為jni的UrlRequestAdapter 設定 http請求型別
  • 4.addRequestHeader為請求裝載header
  • 5.mUploadDataStream讀取並把body的資料快取到jni的UploadDataStream中.
  • 6.呼叫startInternalLocked也就是CronetUrlRequest的start

CronetURLRequestAdapter 建立

```java static jlong JNI_CronetUrlRequest_CreateRequestAdapter( JNIEnv env, const JavaParamRef& jurl_request, jlong jurl_request_context_adapter, const JavaParamRef& jurl_string, jint jpriority, jboolean jdisable_cache, jboolean jdisable_connection_migration, jboolean jenable_metrics, jboolean jtraffic_stats_tag_set, jint jtraffic_stats_tag, jboolean jtraffic_stats_uid_set, jint jtraffic_stats_uid, jint jidempotency) { CronetURLRequestContextAdapter context_adapter = reinterpret_cast( jurl_request_context_adapter);

CronetURLRequestAdapter* adapter = new CronetURLRequestAdapter( context_adapter, env, jurl_request, url, static_cast(jpriority), jdisable_cache, jdisable_connection_migration, jenable_metrics, jtraffic_stats_tag_set, jtraffic_stats_tag, jtraffic_stats_uid_set, jtraffic_stats_uid, static_cast(jidempotency));

return reinterpret_cast(adapter); } ```

```java CronetURLRequestAdapter::CronetURLRequestAdapter( CronetURLRequestContextAdapter context, JNIEnv env, jobject jurl_request, const GURL& url, net::RequestPriority priority, jboolean jdisable_cache, jboolean jdisable_connection_migration, jboolean jenable_metrics, jboolean jtraffic_stats_tag_set, jint jtraffic_stats_tag, jboolean jtraffic_stats_uid_set, jint jtraffic_stats_uid, net::Idempotency idempotency) : request_( new CronetURLRequest(context->cronet_url_request_context(), std::unique_ptr(this), url, priority, jdisable_cache == JNI_TRUE, jdisable_connection_migration == JNI_TRUE, jenable_metrics == JNI_TRUE, jtraffic_stats_tag_set == JNI_TRUE, jtraffic_stats_tag, jtraffic_stats_uid_set == JNI_TRUE, jtraffic_stats_uid, idempotency)) { owner_.Reset(env, jurl_request); }

```

CronetURLRequestContextAdapter 持有一個 CronetURLRequest物件。剛剛好對應java層中的CronetURLRequest。整個請求的發起就是從CronetURLRequest開始。

CronetURLRequest 建構函式

cpp CronetURLRequest::CronetURLRequest(CronetURLRequestContext* context, std::unique_ptr<Callback> callback, const GURL& url, net::RequestPriority priority, bool disable_cache, bool disable_connection_migration, bool enable_metrics, bool traffic_stats_tag_set, int32_t traffic_stats_tag, bool traffic_stats_uid_set, int32_t traffic_stats_uid, net::Idempotency idempotency) : context_(context), network_tasks_(std::move(callback), url, priority, CalculateLoadFlags(context->default_load_flags(), disable_cache, disable_connection_migration), enable_metrics, traffic_stats_tag_set, traffic_stats_tag, traffic_stats_uid_set, traffic_stats_uid, idempotency), initial_method_("GET"), initial_request_headers_(std::make_unique<net::HttpRequestHeaders>()) { DCHECK(!context_->IsOnNetworkThread()); }

能看到CronetURLRequest預設設定GET http的方法,同時建立HttpRequestHeaders接受Http協議的頭部資訊。

CronetURLRequest start

java方法CronetUrlRequestJni.get().start所對應的jni方法如下,也就是CronetURLRequest的start方法。

cpp void CronetURLRequestAdapter::Start(JNIEnv* env, const JavaParamRef<jobject>& jcaller) { request_->Start(); }

cpp void CronetURLRequest::Start() { DCHECK(!context_->IsOnNetworkThread()); context_->PostTaskToNetworkThread( FROM_HERE, base::BindOnce(&CronetURLRequest::NetworkTasks::Start, base::Unretained(&network_tasks_), base::Unretained(context_), initial_method_, std::move(initial_request_headers_), std::move(upload_))); }

start方法其實就是切換到network執行緒。呼叫NetworkTasks名為start類方法。

NetworkTasks Start

```cpp void CronetURLRequest::NetworkTasks::Start( CronetURLRequestContext* context, const std::string& method, std::unique_ptr request_headers, std::unique_ptr upload) {

url_request_ = context->GetURLRequestContext()->CreateRequest( initial_url_, net::DEFAULT_PRIORITY, this, MISSING_TRAFFIC_ANNOTATION); url_request_->SetLoadFlags(initial_load_flags_); url_request_->set_method(method); url_request_->SetExtraRequestHeaders(*request_headers); url_request_->SetPriority(initial_priority_); url_request_->SetIdempotency(idempotency_); std::string referer; if (request_headers->GetHeader(net::HttpRequestHeaders::kReferer, &referer)) { url_request_->SetReferrer(referer); } if (upload) url_request_->set_upload(std::move(upload)); if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {

if BUILDFLAG(IS_ANDROID)

url_request_->set_socket_tag(net::SocketTag(
    traffic_stats_uid_set_ ? traffic_stats_uid_ : net::SocketTag::UNSET_UID,
    traffic_stats_tag_set_ ? traffic_stats_tag_
                           : net::SocketTag::UNSET_TAG));

else

...

endif

} url_request_->Start(); } ```

GetURLRequestContext()->CreateRequest創造一個URLRequest物件。將儲存在CronetURLRequest填充到URLRequest中,並呼叫這個物件的start方法。

而這個URLRequest 你可以看成Cronet的核心對外的最重要的介面。因為iOS的模組最終也是對接到URLRequest物件中。

URLRequest 的標頭檔案

```cpp class NET_EXPORT URLRequest : public base::SupportsUserData { public:

typedef URLRequestJob(ProtocolFactory)(URLRequest request, const std::string& scheme);

static constexpr int kMaxRedirects = 20;

...

URLRequest(const URLRequest&) = delete; URLRequest& operator=(const URLRequest&) = delete; ... ~URLRequest() override;

...

protected: ...

private: friend class URLRequestJob; friend class URLRequestContext;

// For testing purposes. // TODO(maksims): Remove this. friend class TestNetworkDelegate;

// URLRequests are always created by calling URLRequestContext::CreateRequest. URLRequest(const GURL& url, RequestPriority priority, Delegate delegate, const URLRequestContext context, NetworkTrafficAnnotationTag traffic_annotation, bool is_for_websockets, absl::optional net_log_source);

... raw_ptr context_;

...

std::unique_ptr job_; std::unique_ptr upload_data_stream_;

std::vector url_chain_; SiteForCookies site_for_cookies_;

... }; ```

在這裡面有3個核心的物件:

  • 1.context_型別是URLRequestContext,該類負責了URLRequest請求過程中需要的上下文,其中有一個核心的核心上下文QuicContext用於quic協議請求的過程
  • 2.job_ 這個物件是URLRequestJob 這是URLRequest真正用於執行請求的任務物件,內建請求任務生命週期
  • 3.upload_data_stream_UploadDataStream,這個資料流是用於儲存post時候的,訊息體。

URLRequest Start

```cpp void URLRequest::Start() { if (status_ != OK) return; ...

...

StartJob(context_->job_factory()->CreateJob(this)); } ```

很見到在這裡獲取job_factory通過CreateJob 建立一個URLRequestJob工作項,並呼叫UrlRequestStartJob啟動URLRequestJob。 簡單看看CreateJob返回的是什麼型別的URLRequestJob.

```cpp std::unique_ptr URLRequestJobFactory::CreateJob( URLRequest* request) const {

if (!request->url().is_valid()) return std::make_unique(request, ERR_INVALID_URL);

if (g_interceptor_for_testing) { std::unique_ptr job( g_interceptor_for_testing->MaybeInterceptRequest(request)); if (job) return job; }

auto it = protocol_handler_map_.find(request->url().scheme()); if (it == protocol_handler_map_.end()) { return std::make_unique(request, ERR_UNKNOWN_URL_SCHEME); }

return it->second->CreateJob(request); } ```

實際上在不同的協議都會對應上不同的URLRequestJob工廠,而這些網路協議建立工廠為ProtocolHandler.這些ProtocolHandler都可以通過設定到protocol_handler_map_中,根據協議頭scheme進行自定義協議實現。

而在這個核心層中,預設自帶了HttpProtocolHandler實現,如下:

```cpp class HttpProtocolHandler : public URLRequestJobFactory::ProtocolHandler { public:

explicit HttpProtocolHandler(bool is_for_websockets) : is_for_websockets_(is_for_websockets) {}

HttpProtocolHandler(const HttpProtocolHandler&) = delete; HttpProtocolHandler& operator=(const HttpProtocolHandler&) = delete; ~HttpProtocolHandler() override = default;

std::unique_ptr CreateJob(URLRequest* request) const override { if (request->is_for_websockets() != is_for_websockets_) { return std::make_unique(request, ERR_UNKNOWN_URL_SCHEME); } return URLRequestHttpJob::Create(request); }

const bool is_for_websockets_; }; ```

能看到預設的 Http對應的協議處理器HttpProtocolHandler,並通過CreateJob建立請求任務對應URLRequestHttpJob。而這個的設定時機:

```cpp URLRequestJobFactory::URLRequestJobFactory() { SetProtocolHandler(url::kHttpScheme, std::make_unique( /is_for_websockets=/false)); SetProtocolHandler(url::kHttpsScheme, std::make_unique( /is_for_websockets=/false));

if BUILDFLAG(ENABLE_WEBSOCKETS)

SetProtocolHandler(url::kWsScheme, std::make_unique( /is_for_websockets=/true)); SetProtocolHandler(url::kWssScheme, std::make_unique( /is_for_websockets=/true));

endif // BUILDFLAG(ENABLE_WEBSOCKETS)

} ``job_factory也就是URLRequestJobFactory`型別,能看到建構函式中預設的設定了http和https,ws,wss的協議處理器。

```cpp void URLRequest::StartJob(std::unique_ptr job) { ... job_ = std::move(job); job_->SetExtraRequestHeaders(extra_request_headers_); job_->SetPriority(priority_); job_->SetRequestHeadersCallback(request_headers_callback_); job_->SetEarlyResponseHeadersCallback(early_response_headers_callback_); job_->SetResponseHeadersCallback(response_headers_callback_);

if (upload_data_stream_.get()) job_->SetUpload(upload_data_stream_.get());

... job_->Start(); } ```

很簡單就是把URLRequest 中的頭部,優先順序,回撥,訊息體的資料流引用資料儲存到URLRequestJob,並呼叫URLRequestJob的Start。此時URLRequestJob一般是指URLRequestHttpJob.

URLRequestHttpJob Start

```cpp void URLRequestHttpJob::Start() {

request_info_.url = request_->url(); request_info_.method = request_->method();

request_info_.network_isolation_key = request_->isolation_info().network_isolation_key(); request_info_.possibly_top_frame_origin = request_->isolation_info().top_frame_origin(); request_info_.is_subframe_document_resource = request_->isolation_info().request_type() == net::IsolationInfo::RequestType::kSubFrame; request_info_.load_flags = request_->load_flags(); request_info_.secure_dns_policy = request_->secure_dns_policy(); request_info_.traffic_annotation = net::MutableNetworkTrafficAnnotationTag(request_->traffic_annotation()); request_info_.socket_tag = request_->socket_tag(); request_info_.idempotency = request_->GetIdempotency();

if BUILDFLAG(ENABLE_REPORTING)

request_info_.reporting_upload_depth = request_->reporting_upload_depth();

endif

bool should_add_cookie_header = ShouldAddCookieHeader();

if (!should_add_cookie_header) { OnGotFirstPartySetMetadata(FirstPartySetMetadata()); return; } absl::optional metadata = cookie_util::ComputeFirstPartySetMetadataMaybeAsync( SchemefulSite(request()->url()), request()->isolation_info(), request()->context()->cookie_store()->cookie_access_delegate(), request()->force_ignore_top_frame_party_for_cookies(), base::BindOnce(&URLRequestHttpJob::OnGotFirstPartySetMetadata, weak_factory_.GetWeakPtr()));

if (metadata.has_value()) OnGotFirstPartySetMetadata(std::move(metadata.value())); } ```

UrlRequest的請求引數儲存到request_info_。如果沒有任何的cookie則直接呼叫OnGotFirstPartySetMetadata,如果存在全域性通用cookie,則把資料儲存到FirstPartySetMetadata。並呼叫OnGotFirstPartySetMetadata.

OnGotFirstPartySetMetadata

```cpp void URLRequestHttpJob::OnGotFirstPartySetMetadata( FirstPartySetMetadata first_party_set_metadata) { first_party_set_metadata_ = std::move(first_party_set_metadata);

request_info_.privacy_mode = DeterminePrivacyMode(); ...

GURL referrer(request_->referrer());

if (referrer.is_valid()) { std::string referer_value = referrer.spec(); request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer, referer_value); }

request_info_.extra_headers.SetHeaderIfMissing( HttpRequestHeaders::kUserAgent, http_user_agent_settings_ ? http_user_agent_settings_->GetUserAgent() : std::string());

AddExtraHeaders();

if (ShouldAddCookieHeader()) {

cookie_partition_key_ =
    absl::make_optional(CookiePartitionKey::FromNetworkIsolationKey(
        request_->isolation_info().network_isolation_key(),
        base::OptionalOrNullptr(
            first_party_set_metadata_.top_frame_owner())));
AddCookieHeaderAndStart();

} else { StartTransaction(); } } ```

  • 1.先通過SetHeader以及AddExtraHeaders設定Referer,GZIP等常用的Header
  • 2.如果存在cookie則通過AddCookieHeaderAndStart新增到Header中Cookie為key的資料集合中。不過
  • 3.StartTransaction啟動事務。

URLRequestHttpJob StartTransaction

```cpp void URLRequestHttpJob::StartTransaction() { ... StartTransactionInternal(); }

void URLRequestHttpJob::StartTransactionInternal() {

int rv;

...

if (transaction_.get()) { rv = transaction_->RestartWithAuth( auth_credentials_, base::BindOnce(&URLRequestHttpJob::OnStartCompleted, base::Unretained(this))); auth_credentials_ = AuthCredentials(); } else {

rv = request_->context()->http_transaction_factory()->CreateTransaction(
    priority_, &transaction_);

...

if (rv == OK) {
  transaction_->SetConnectedCallback(base::BindRepeating(
      &URLRequestHttpJob::NotifyConnectedCallback, base::Unretained(this)));
  transaction_->SetRequestHeadersCallback(request_headers_callback_);
  transaction_->SetEarlyResponseHeadersCallback(
      early_response_headers_callback_);
  transaction_->SetResponseHeadersCallback(response_headers_callback_);

  if (!throttling_entry_.get() ||
      !throttling_entry_->ShouldRejectRequest(*request_)) {
    rv = transaction_->Start(
        &request_info_,
        base::BindOnce(&URLRequestHttpJob::OnStartCompleted,
                       base::Unretained(this)),
        request_->net_log());
    start_time_ = base::TimeTicks::Now();
  } else {
    // Special error code for the exponential back-off module.
    rv = ERR_TEMPORARILY_THROTTLED;
  }
}

}

if (rv == ERR_IO_PENDING) return;

// The transaction started synchronously, but we need to notify the // URLRequest delegate via the message loop. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&URLRequestHttpJob::OnStartCompleted, weak_factory_.GetWeakPtr(), rv)); } ```

能看到這個過程中存在一個核心的物件transaction_也就是HttpTransaction。之後請求Job工作項,就將請求委託給HttpTransaction.

如果發現URLRequestHttpJob已經存在了HttpTransaction,那麼就會呼叫HttpTransactionRestartWithAuth重新啟動並且校驗許可權。

如果發現沒有建立,則呼叫事務工廠CreateTransaction建立HttpTransaction,然後呼叫Start方法正式啟動事務,開始請求。

總結

首先進行一個初步的總結,到了HttpTransaction之後,就會開始流轉請求的生命週期,然後進行quic協議的初始化,執行quic的請求。

不過限於篇幅,以及Cronet設計上的確實比較冗長,這裡先做一個簡單的總結先:

可以將Cronet的設計組合看成3層:

  • 1.java的api層
  • 2.用於連通java和native的jni的adapter層
  • 3.通用於所有平臺的核心層

java層會通過反射嘗試獲取不同環境依賴下的cronetProvider,也就是Cornet的核心提供器。有的是依賴Google環境,有的可以自己自己直接依賴native的包,都沒有則使用預設的 android自帶的網路請求。

jni層,實際上就是末尾帶上了Adapter的類以及和java層中相同類名的類,這些類一般不做任何事情,一般會包裹一個對應相同名字的cpp物件在native中,並且把相同的行為賦予給Adpater以及對應的native物件。

  • java層CronetUrlRequestContext 會對應上 jni中的CronetURLRequestContextAdapter作為樞紐,間接控制native中的CronetURLRequestContext。而CronetURLRequestContext則是控制了整個請求的上下文

  • java層CronetUrlRequest 會對應上jni中的CronetURLRequestAdapter,並間接控制native層的CronetUrlRequest物件。

而這個物件最終會控制native層的UrlRequest,而這個物件最終會通向Cronet的核心層。並且會從thridParty資料夾中找到quic協議相關的處理。

後續的文章將會繼續揭曉HttpTransaction如何進行事務流轉,並且quic是如何執行。