Chromium net iOS移植与使用

Chromium浏览器的网络库是一个功能非常强大的网络库,它支持的网络协议非常多,除了常见的HTTP/1.1,它还支持HTTP/2,QUIC等比较新的协议。Chromium net Android移植指南一文介绍了如何在Android平台上移植和使用Chromium的网络库,本文作为姊妹篇,对如何在iOS平台上移植和使用Chromium网络库进行详细介绍。

Chromium net编译

首先我们要准备好Chromium源码, 准备好源码后,我们便可以进行net库的编译工作了。

对编译进行配置工作

gn args out/default

通过上述命令生成.ninja编译的配置文件后,可以根据需要自行配置:

# This file was generated by setup-gn.py. Do not edit
# but instead use ~/.setup-gn or $repo/.setup-gn files
# to configure settings.

is_debug = false
enable_dsyms = false
enable_stripping = enable_dsyms
is_official_build = false
is_chrome_branded = is_official_build
use_xcode_clang = is_official_build
target_cpu = "arm64"
target_os = "ios"
disable_file_support = true
disable_ftp_support = true
enable_websockets = false
enbale_nacl = false
use_platform_icu_alternatives = true

关键点主要有以下几个:

  • is_debug: 默认是true,表示编译Debug版本
  • is_component_build: 默认是ture, 表示components中声明的所有targets(如cronet等)会被编译成动态链接库。当设置为false时,会被编译成表态链接库。
  • target_cpu:目标架构
  • target_os:目标平台
  • disable_file_support,disable_ftp_support,enable_websockets:用来选择性地支持file, ftp, websockets协议,可以通过此配置项,裁剪这些协议,达到减小最终编译模块的size。
  • use_platform_icu_alternaives:置为true,表明chromium代码库中不使用ICU,从而也可以以减小最终模块的size。

产生ninja文件

gn gen out/default

编译net库

ninja -C out/default net

通过ninja命令,指明编译的模块net,即可编译出libnet.a及其所依赖的所有其他模块。

Chromium net集成使用

为了使用net库,我们需要将libnet.a及其所依赖的所有其他lib都引入工程中,同时也不可避免地要将net库所依赖的头文件都要引入到项目中。因此,在集成net库时,我们需要提取其所依赖的libs和头文件。

提取头文件

这里我们需要借助强大的gn工具帮我们做些分析。要提到的便是gn desc命令,它可以帮我们分析出我们所指定module依赖的源文件、依赖库、预定义宏、编译参数、以及所依赖的iOS的Frameworks。

#gn desc out/default net

>输出
Target //net:net
Type: static_library
Toolchain: //build/toolchain/mac:ios_clang_arm64

visibility
  *

testonly
  false

check_includes
  true

allow_circular_includes_from

sources
  //net/base/address_family.cc
  //net/base/address_family.h
  ...

public
  [All headers listed in the sources are public.]

configs (in order applying, try also --tree)
  //build/config:feature_flags
  ...

public_configs (in order applying, try also --tree)
  //net:net_config
  ...

outputs
  //out/default/obj/net/libnet.a

asmflags
  -fno-strict-aliasing
  ...

cflags
  -fno-strict-aliasing
  ...

cflags_c
  -std=c99

cflags_cc
  -fno-threadsafe-statics
  ...
  -fno-rtti
  ...

cflags_objc
  -std=c99

cflags_objcc
  -fno-threadsafe-statics
  ...

defines
  V8_DEPRECATION_WARNINGS
  ...

include_dirs
  //
  //out/default/gen/
  ...

ldflags
  -arch
  ...

Direct dependencies (try also "--all", "--tree", or even "--all --tree")
  //base:base
  ...

libs
  CFNetwork.framework
  MobileCoreServices.framework
  Security.framework
  SystemConfiguration.framework
  resolv
  CoreFoundation.framework

从这个输出,我们可以得到net所依赖的头文件、头文件目录、其他的targets等。在添加对头文件的依赖时,有两种方式,一种是使用脚本分析gn desc的结果,完全提取出头文件添加到我们的项目中; 或者直接更简单一点,根据上面的include_dirs的路径,我们可以获取所依赖的头文件目录,在Build settings中设置Header Path也可以。这里主要介绍下如何使用脚本提取所依赖的所有头文件。

下面是Android端同事提供的脚本chromium_mod_headers_extracter.py,共享出来。

#!/usr/bin/env python

import os
import shutil
import sys

def print_usage_and_exit():
    print sys.argv[0] + " [chromium_src_root]" + "[out_dir]" + " [target_name]" + " [targetroot]"
    exit(1)

def copy_file(src_file_path, target_file_path):
    if os.path.exists(target_file_path):
        return
    if not os.path.exists(src_file_path):
        return
    target_dir_path = os.path.dirname(target_file_path)
    if not os.path.exists(target_dir_path):
        os.makedirs(target_dir_path)

    shutil.copy(src_file_path, target_dir_path)

def copy_all_files(source_dir, all_files, target_dir):
    for one_file in all_files:
        source_path = source_dir + os.path.sep + one_file
        target_path = target_dir + os.path.sep + one_file
        copy_file(source_path, target_path)

if __name__ == "__main__":
    if len(sys.argv) < 4 or len(sys.argv) > 5:
        print_usage_and_exit()
    chromium_src_root = sys.argv[1]
    out_dir = sys.argv[2]
    target_name = sys.argv[3]
    target_root_path = "."
    if len(sys.argv) == 5:
        target_root_path = sys.argv[4]
    target_root_path = os.path.abspath(target_root_path)

    os.chdir(chromium_src_root)

    cmd = "gn desc " + out_dir + " " + target_name
    outputs = os.popen(cmd).readlines()
    source_start = False
    all_headers = []

    public_start = False
    public_headers = []

    for output_line in outputs:
        output_line = output_line.strip()
        if output_line.startswith("sources"):
            source_start = True
            continue
        elif source_start and len(output_line) == 0:
            source_start = False
            continue
        elif source_start and output_line.endswith(".h"):
            output_line = output_line[1:]
            all_headers.append(output_line)
        elif output_line == "public":
            public_start = True
            continue
        elif public_start and len(output_line) == 0:
            public_start = False
            continue
        elif public_start:
            public_headers.append(output_line)

    if len(public_headers) == 1:
        public_headers = all_headers
    if len(public_headers) > 1:
        copy_all_files(chromium_src_root, public_headers, target_dir=target_root_path)

脚本执行方式: script <chromium代码库的src目录路径> <编译输出的目录路径> <模块名> [保存头文件的目标目录路径],头文件存储的目标路径默认是当前路径。

因为这些头文件与baseurl模块的头文件是相互依赖的,所以我们要提取net,base,url这三个模块的头文件:

python chromium_mod_headers_extracter.py /xxx/xxx/chromium-ios/src out/default net
python chromium_mod_headers_extracter.py /xxx/xxx/chromium-ios/src out/default base
python chromium_mod_headers_extracter.py /xxx/xxx/chromium-ios/src out/default url

但是,gn desc也是有一些小缺陷的,分析结果可以会有一些遗漏的地方,比如源文件、依赖的targets等等,实际使用的过程中,需根据需要手动去补充缺失的部分。

提取依赖库

对于获取依赖的libs,我们并不能直观地根据gn desc的结果获得。这时可以通过我们gn gen所生成的out\default\obj\net\net.ninja文件分析得到,ninja文件类似我们常见的makefile文件,包含更加细致的build flags, Predefines, 以及所有的依赖文件,因此,我们也可以通过脚本去分析ninja文件,提取所有的libs,这里也给出一个简单的脚本Extract_libs.py,提取所有的dependencies,脚本用法:script <编译输出的目录路径>

python Extract_libs.py out/default

这里也贴出对应的python脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import shutil

libarray = [
    "libnet.a",
    "libbase.a",
    "libbrotli.a",
    "libbase_i18n.a",
    "libbase_paths.a",
    "libbase_static.a",
    "libboringssl.a",
    "libchrome_zlib.a",
    "libcrcrypto.a",
    "libdynamic_annotations.a",
    "libicui18n.a",
    "libicuuc.a",
    "libprefs.a",
    "libprotobuf_lite.a",
    "libsdch.a",
    "liburl.a",
    "libzlib_x86_simd.a",
    "libevent.a",
    "libmodp_b64.a",
    "libnet_quic_proto.a",
    "libprefs.a"
]

dict = {}

def existFile(filename, path):
    if filename in libarray:
        dict[filename] = path
        return True
    return False


def looklib(rootDir):
    for lists in os.listdir(rootDir):
        path = os.path.join(rootDir, lists)
        filename = os.path.basename(path)

        ext = os.path.splitext(filename)[1]
        if ext == '.a':
            if filename not in libarray:
                #print(path)
                pass

            if existFile(filename, path):
                shutil.copy(path, './dst')
                pass

        if os.path.isdir(path):
            print("dir:%s" % path)
            looklib(path)


if __name__ == '__main__':
    looklib(sys.argv[1])

至此我们已经提供了所有依赖的头文件和libs,可以正式使用net库了。

Chromium net集成

我们新建一个XCode工程后,再在工程目录下新建一个Include目录和libs目录,将上一步提取出来的头文件和libs都放到对应的目录中。并在进行Build Settings页进行设置:

引入libs


设置Header Path


Chromium net的简单使用

Chromium net主要接口是URLRequestURLRequestContext,以及URLRequest的代理类:URLRequest::DelegateURLRequest负责启动URL请求的工作,并通过delegate方法告知请求响应开始、结束、出错等事件。URLRequestContext包含实现URL请求所需要的相关上下文,比如cookies、Host Resolver、cache等等,同时,它也负责创建URLRequest对象的工作。URLRequest之间可以共享context对象,也可以独享一个context对象。

在使用Chromium net实现网络请求工作时,主要完成三点:

  • 创建URLRequestContext对象
  • 创建URLRequest对象
  • 继承并实现URLRequest::Delegate

下面简单给出使用Demo:

URLRequest::Delegate继承与实现

class SimpleRequestDelegate : public URLRequest::Delegate{
    public:
        SimpleRequestDelegate();
        ~SimpleRequestDelegate() override;

        // URLRequest::Delegate:
        void OnReceivedRedirect(URLRequest* request,
                                const RedirectInfo& redirect_info,
                                bool* defer_redirect) override;
        void OnBeforeNetworkStart(URLRequest* request, bool* defer) ;
        void OnAuthRequired(URLRequest* request,
                            AuthChallengeInfo* auth_info) override;
        void OnSSLCertificateError(URLRequest* request,
                                   const SSLInfo& ssl_info,
                                   bool fatal) override;
        void OnResponseStarted(URLRequest* request) override;
        void OnReadCompleted(URLRequest* request, int bytes_read) override;

    private:
        static const int kBufferSize = 4096;

        virtual void OnResponseCompleted(URLRequest* request);

        // tracks status of callbacks
        int response_started_count_;
        int received_bytes_count_;
        int received_redirect_count_;
        int received_before_network_start_count_;
        bool received_data_before_response_;
        bool request_failed_;
        bool have_certificate_errors_;
        bool certificate_errors_are_fatal_;
        bool auth_required_;
        std::string data_received_;
        bool have_full_request_headers_;
        HttpRequestHeaders full_request_headers_;

        // our read buffer
        scoped_refptr<IOBuffer> buf_;
    };

  void SimpleRequestDelegate::OnReceivedRedirect(URLRequest* request,
                                          const RedirectInfo& redirect_info,
                                          bool* defer_redirect) {
        printf("OnReceivedRedirect ...\n");
        have_full_request_headers_ =
        request->GetFullRequestHeaders(&full_request_headers_);

        received_redirect_count_++;
        if (quit_on_redirect_) {
            *defer_redirect = true;
            base::ThreadTaskRunnerHandle::Get()->PostTask(
                                                          FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
        } else if (cancel_in_rr_) {
            request->Cancel();
        }
    }

    void SimpleRequestDelegate::OnBeforeNetworkStart(URLRequest* request, bool* defer) {
        printf("OnBeforeNetworkStart ...\n");
        received_before_network_start_count_++;
        if (quit_on_before_network_start_) {
            *defer = true;
            base::ThreadTaskRunnerHandle::Get()->PostTask(
                                                          FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
        }
    }

    void SimpleRequestDelegate::OnAuthRequired(URLRequest* request,
                                      AuthChallengeInfo* auth_info) {
        printf("OnAuthRequired ...\n");
        auth_required_ = true;
        if (quit_on_auth_required_) {
            base::ThreadTaskRunnerHandle::Get()->PostTask(
                                                          FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
            return;
        }
        if (!credentials_.Empty()) {
            request->SetAuth(credentials_);
        } else {
            request->CancelAuth();
        }
    }

    void SimpleRequestDelegate::OnSSLCertificateError(URLRequest* request,
                                             const SSLInfo& ssl_info,
                                             bool fatal) {
        printf("OnSSLCertificateError ...\n");
        // The caller can control whether it needs all SSL requests to go through,
        // independent of any possible errors, or whether it wants SSL errors to
        // cancel the request.
        have_certificate_errors_ = true;
        certificate_errors_are_fatal_ = fatal;
        if (allow_certificate_errors_)
            request->ContinueDespiteLastError();
        else
            request->Cancel();
    }

    void SimpleRequestDelegate::OnResponseStarted(URLRequest* request) {
        printf("OnResponseStarted ...\n");
        // It doesn't make sense for the request to have IO pending at this point.
        DCHECK(!request->status().is_io_pending());

        have_full_request_headers_ =
        request->GetFullRequestHeaders(&full_request_headers_);

        response_started_count_++;
        if (cancel_in_rs_) {
            request->Cancel();
            OnResponseCompleted(request);
        } else if (!request->status().is_success()) {
            DCHECK(request->status().status() == URLRequestStatus::FAILED ||
                   request->status().status() == URLRequestStatus::CANCELED);
            request_failed_ = true;
            OnResponseCompleted(request);
        } else {
            // Initiate the first read.
            int bytes_read = 0;
            if (request->Read(buf_.get(), kBufferSize, &bytes_read))
                OnReadCompleted(request, bytes_read);
            else if (!request->status().is_io_pending())
                OnResponseCompleted(request);
        }
    }

    void SimpleRequestDelegate::OnReadCompleted(URLRequest* request, int bytes_read) {
        printf("OnReadCompleted ...\n");
        DCHECK(!request->status().is_io_pending());

        if (bytes_read >= 0) {
            // There is data to read.
            received_bytes_count_ += bytes_read;
            data_received_.append(buf_->data(), bytes_read);
        }

        // If it was not end of stream, request to read more.
        if (request->status().is_success() && bytes_read > 0) {
            bytes_read = 0;
            while (request->Read(buf_.get(), kBufferSize, &bytes_read)) {
                if (bytes_read > 0) {
                    data_received_.append(buf_->data(), bytes_read);
                    received_bytes_count_ += bytes_read;
                } else {
                    break;
                }
            }
        }
        if (!request->status().is_io_pending())
            OnResponseCompleted(request);
        else if (cancel_in_rd_pending_)
            request->Cancel();
    }

创建并初始化URLRequestContext对象

class TestURLRequestContext : public URLRequestContext{
    public:
        TestURLRequestContext();
        explicit TestURLRequestContext(bool delay_initialization);
        ~TestURLRequestContext() override;

        void Init();

        ClientSocketFactory* client_socket_factory() {
            return client_socket_factory_;
        }
        void set_client_socket_factory(ClientSocketFactory* factory) {
            client_socket_factory_ = factory;
        }

        ProxyDelegate* proxy_delegate() { return proxy_delegate_; }

        void set_proxy_delegate(ProxyDelegate* proxy_delegate) {
            proxy_delegate_ = proxy_delegate;
        }

        void set_http_network_session_params(
                                             std::unique_ptr<HttpNetworkSession::Params> params) {
            http_network_session_params_ = std::move(params);
        }

        void SetSdchManager(std::unique_ptr<SdchManager> sdch_manager) {
            context_storage_.set_sdch_manager(std::move(sdch_manager));
        }

        void SetCTPolicyEnforcer(
                                 std::unique_ptr<CTPolicyEnforcer> ct_policy_enforcer) {
            context_storage_.set_ct_policy_enforcer(std::move(ct_policy_enforcer));
        }


    private:
        bool initialized_ = false;

        ClientSocketFactory* client_socket_factory_ = nullptr;

        ProxyDelegate* proxy_delegate_ = nullptr;

        std::unique_ptr<HttpNetworkSession::Params> http_network_session_params_;

    protected:
        URLRequestContextStorage context_storage_;
    };
    TestURLRequestContext::TestURLRequestContext() : TestURLRequestContext(false) {}

    TestURLRequestContext::TestURLRequestContext(bool delay_initialization)
    : context_storage_(this) {

    }

    TestURLRequestContext::~TestURLRequestContext() {

    }

    void TestURLRequestContext::Init() {
        initialized_ = true;

        if (!host_resolver())
            context_storage_.set_host_resolver(HostResolver::CreateDefaultResolver(net_log()));

        if (!proxy_service())
        {
            context_storage_.set_proxy_service(ProxyService::CreateDirect());
        }
        if (!cert_verifier())
            context_storage_.set_cert_verifier(CertVerifier::CreateDefault());
        if (!transport_security_state()) {
            context_storage_.set_transport_security_state(
                                                          base::WrapUnique(new TransportSecurityState()));
        }

        if (!cert_transparency_verifier()) {
            context_storage_.set_cert_transparency_verifier(
                                                            base::WrapUnique(new MultiLogCTVerifier()));
        }
        if (!ct_policy_enforcer()) {
            context_storage_.set_ct_policy_enforcer(
                                                    base::WrapUnique(new CTPolicyEnforcer));
        }
        if (!ssl_config_service())
            context_storage_.set_ssl_config_service(new SSLConfigServiceDefaults());
        if (!http_auth_handler_factory()) {
            context_storage_.set_http_auth_handler_factory(
                                                           HttpAuthHandlerFactory::CreateDefault(host_resolver()));
        }
        if (!http_server_properties()) {
            context_storage_.set_http_server_properties(
                                                        std::unique_ptr<HttpServerProperties>(new HttpServerPropertiesImpl()));
        }
        // In-memory cookie store.
        if (!cookie_store()) {
            context_storage_.set_cookie_store(
                                              base::WrapUnique(new CookieMonster(nullptr, nullptr)));
        }
        // In-memory Channel ID service.  Must be created before the
        // HttpNetworkSession.
        if (!channel_id_service()) {
            context_storage_.set_channel_id_service(base::WrapUnique(
                                                                     new ChannelIDService(new DefaultChannelIDStore(nullptr),
                                                                                          base::WorkerPool::GetTaskRunner(true))));
        }
        //
        if (http_transaction_factory()) {
            // Make sure we haven't been passed an object we're not going to use.

        } else {
            HttpNetworkSession::Params params;

            if (http_network_session_params_)
                params = *http_network_session_params_;
            params.client_socket_factory = client_socket_factory();
            params.proxy_delegate = proxy_delegate();
            params.host_resolver = host_resolver();
            params.cert_verifier = cert_verifier();
            params.cert_transparency_verifier = cert_transparency_verifier();
            params.ct_policy_enforcer = ct_policy_enforcer();
            params.transport_security_state = transport_security_state();
            params.proxy_service = proxy_service();
            params.ssl_config_service = ssl_config_service();
            params.http_auth_handler_factory = http_auth_handler_factory();
            params.http_server_properties = http_server_properties();
            params.net_log = net_log();
            params.channel_id_service = channel_id_service();
            context_storage_.set_http_network_session(
                                                      base::WrapUnique(new HttpNetworkSession(params)));
            context_storage_.set_http_transaction_factory(base::WrapUnique(
                                                                           new HttpCache(context_storage_.http_network_session(),
                                                                                         HttpCache::DefaultBackend::InMemory(0), false)));
        }
        if (!http_user_agent_settings()) {
//            context_storage_.set_http_user_agent_settings(base::WrapUnique(
//                                                                           new StaticHttpUserAgentSettings("en-us,fr", std::string())));
        }
        if (!job_factory()) {
            context_storage_.set_job_factory(base::WrapUnique(new URLRequestJobFactoryImpl()));
        }
}

创建URLRequest并Start

-(void)startRequestTest(const char * url){

    SimpleRequestDelegate d;
    TestURLRequestContext default_context_;
    URLRequestJobFactoryImpl* job_factory_impl_;
    std::unique_ptr<URLRequestJobFactory> job_factory_;

    job_factory_impl_ = new URLRequestJobFactoryImpl();
    job_factory_.reset(job_factory_impl_);

    job_factory_impl_->SetProtocolHandler("http", base::WrapUnique(new TestJobInterceptor));

    default_context_.set_job_factory(job_factory_.get());

    default_context_.Init();
    std::unique_ptr<URLRequest> r(default_context_.CreateRequest(GURL(url), DEFAULT_PRIORITY, &d));
    LoadStateWithParam load_state = r->GetLoadState();
    r->Start();

    base::RunLoop().Run();

}

Note: 可能在build的时候会遇到一些问题,请确保Xcode工程设置与gn descnet.ninja分析出来的编译参数保持一致。我当时就遇到由于C++ RTT的开头设置而导致的问题,这里面需要将此开头disable掉。

小结

本文只是简单介绍了如何编译并集成使用Chromium Net库,对于使用部分,只是基于如何通过它完成Request给出了一个Demo。如果需要在我们的App中真正使用Chromium Net Stack, 并发挥好它的性能优势和开源特性。我们还有必要将Chromium Net库完全独立出来,甚至进行封装成。而不再依赖于庞大的Chromium的源码。一来,方便我们开发;二来,方便我们做性能优化。

所以,下一篇,让我们一起来剥离网络库。


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者何慧授权发布。