Chromium浏览器的网络库是一个功能非常强大的网络库,它支持的网络协议非常多,除了常见的HTTP/1.1,它还支持HTTP/2,QUIC等比较新的协议。Chromium net Android移植指南一文介绍了如何在Android平台上移植和使用Chromium的网络库,本文作为姊妹篇,对如何在iOS平台上移植和使用Chromium网络库进行详细介绍。
首先我们要准备好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。
gn gen out/default
ninja -C out/default net
通过ninja命令,指明编译的模块net
,即可编译出libnet.a
及其所依赖的所有其他模块。
为了使用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目录路径> <编译输出的目录路径> <模块名> [保存头文件的目标目录路径]
,头文件存储的目标路径默认是当前路径。
因为这些头文件与base
和url
模块的头文件是相互依赖的,所以我们要提取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库了。
我们新建一个XCode工程后,再在工程目录下新建一个Include
目录和libs
目录,将上一步提取出来的头文件和libs都放到对应的目录中。并在进行Build Settings
页进行设置:
Chromium net主要接口是URLRequest
和URLRequestContext
,以及URLRequest的代理类:URLRequest::Delegate
。URLRequest
负责启动URL请求的工作,并通过delegate方法告知请求响应开始、结束、出错等事件。URLRequestContext
包含实现URL请求所需要的相关上下文,比如cookies、Host Resolver、cache等等,同时,它也负责创建URLRequest
对象的工作。URLRequest
之间可以共享context对象,也可以独享一个context对象。
在使用Chromium net实现网络请求工作时,主要完成三点:
URLRequestContext
对象URLRequest
对象URLRequest::Delegate
类
下面简单给出使用Demo:
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();
}
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()));
}
}
-(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 desc
或net.ninja
分析出来的编译参数保持一致。我当时就遇到由于C++ RTT
的开头设置而导致的问题,这里面需要将此开头disable掉。
本文只是简单介绍了如何编译并集成使用Chromium Net库,对于使用部分,只是基于如何通过它完成Request给出了一个Demo。如果需要在我们的App中真正使用Chromium Net Stack, 并发挥好它的性能优势和开源特性。我们还有必要将Chromium Net库完全独立出来,甚至进行封装成。而不再依赖于庞大的Chromium的源码。一来,方便我们开发;二来,方便我们做性能优化。
所以,下一篇,让我们一起来剥离网络库。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者何慧授权发布。