Cronet-httpCache基本逻辑

背景

对Cronet中如何做http cache做了一个梳理解析.

先介绍一下Cronet网络库的启动流程
Cronet-httpcache
以Android举例,如上图,Cronet是一个单线程网络库,在java层存在唯一的CronetEngine,在C++层存在唯一的URLRequestContext,它们是网络库最核心的上下文。

代码解析

我们重点关注一下httpCache是如何运作的

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
40
41
42
43
44
45
46
// cronetEngine.java
public Builder(Context context) {
mContext = context;
setLibraryName("cronet");
setApplicationName("");
enableLegacyMode(false);
enableQUIC(false);
enableHTTP2(true);
enableSDCH(false);
//初始化的时候,默认关闭cache,第二个参数是cache的size,也默认是0
enableHttpCache(HTTP_CACHE_DISABLED, 0);
}
/* 端上实际通过调用这个成员方法,开启httpcache,并传入cache的size */
public Builder enableHttpCache(@HttpCacheSetting int cacheMode, long maxSize) {
if (cacheMode == HTTP_CACHE_DISK || cacheMode == HTTP_CACHE_DISK_NO_HTTP) {
if (storagePath() == null) {
throw new IllegalArgumentException("Storage path must be set");
}
} else {
if (storagePath() != null) {
throw new IllegalArgumentException("Storage path must not be set");
}
}
mDisableCache =
(cacheMode == HTTP_CACHE_DISABLED || cacheMode == HTTP_CACHE_DISK_NO_HTTP);
mHttpCacheMaxSize = maxSize;
/*
状态共用四种,分别表示关闭、disk模式(no http)、disk模式2、内存模式
*/
switch (cacheMode) {
case HTTP_CACHE_DISABLED:
mHttpCacheMode = HttpCacheType.DISABLED;
break;
case HTTP_CACHE_DISK_NO_HTTP:
case HTTP_CACHE_DISK:
mHttpCacheMode = HttpCacheType.DISK;
break;
case HTTP_CACHE_IN_MEMORY:
mHttpCacheMode = HttpCacheType.MEMORY;
break;
default:
throw new IllegalArgumentException("Unknown cache mode");
}
return this;
}

配置将会通过jni接口传递到C++层,会先初始化URLRequestContextConfig,传入必要配置,之后调用ConfigureURLRequestContextBuilder进行更精细的配置传递

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
// URLRequestContextConfig.cc
void URLRequestContextConfig::ConfigureURLRequestContextBuilder(
net::URLRequestContextBuilder* context_builder,
net::NetLog* net_log) {
std::string config_cache;
/* 把httpcahce的配置传递给context_builder(在adapter持有) */
if (http_cache != DISABLED) {
net::URLRequestContextBuilder::HttpCacheParams cache_params;
if (http_cache == DISK && !storage_path.empty()) {
cache_params.type = net::URLRequestContextBuilder::HttpCacheParams::DISK;
cache_params.path = base::FilePath(storage_path);
} else {
cache_params.type =
net::URLRequestContextBuilder::HttpCacheParams::IN_MEMORY;
}
cache_params.max_size = http_cache_max_size;
context_builder->EnableHttpCache(cache_params);
} else {
context_builder->DisableHttpCache();
}
context_builder->set_user_agent(user_agent);
context_builder->SetSpdyAndQuicEnabled(enable_spdy, enable_quic);
context_builder->set_sdch_enabled(enable_sdch);
if (enable_quic)
context_builder->set_quic_user_agent_id(quic_user_agent_id);
ParseAndSetExperimentalOptions(experimental_options, context_builder,
net_log);
if (mock_cert_verifier)
context_builder->SetCertVerifier(std::move(mock_cert_verifier));
// TODO(mef): Use |config| to set cookies.
}

把所有配置都消化好了之后,调用其build()方法,构建出url_request_context

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
// url_request_context_builder.cc
scoped_ptr<URLRequestContext> URLRequestContextBuilder::Build() {
/* 省略部分代码 */
/* 根据httpcache的配置,创建相应的http_transaction_factory
如果打开了cache,则根据是disk还是memory,创建不同的backend(我理解就是cache存储结构的封装)
并且,在创建transaction的时候,传入的是http_cache_transaction.
如果关闭,则创建的http_network_transaction,纯走网络
*/
scoped_ptr<HttpTransactionFactory> http_transaction_factory;
if (http_cache_enabled_) {
scoped_ptr<HttpCache::BackendFactory> http_cache_backend;
if (http_cache_params_.type != HttpCacheParams::IN_MEMORY) {
BackendType backend_type =
http_cache_params_.type == HttpCacheParams::DISK
? CACHE_BACKEND_DEFAULT
: CACHE_BACKEND_SIMPLE;
http_cache_backend.reset(new HttpCache::DefaultBackend(
DISK_CACHE, backend_type, http_cache_params_.path,
http_cache_params_.max_size, context->GetFileTaskRunner()));
} else {
http_cache_backend =
HttpCache::DefaultBackend::InMemory(http_cache_params_.max_size);
}
http_transaction_factory.reset(new HttpCache(
storage->http_network_session(), std::move(http_cache_backend), true));
} else {
http_transaction_factory.reset(
new HttpNetworkLayer(storage->http_network_session()));
}
storage->set_http_transaction_factory(std::move(http_transaction_factory));
URLRequestJobFactoryImpl* job_factory = new URLRequestJobFactoryImpl;
if (data_enabled_)
job_factory->SetProtocolHandler("data",
make_scoped_ptr(new DataProtocolHandler));
/* 省略部分代码 */
return std::move(context);
}

为什么POST请求不支持httpCache

以上已经讲述了是否使用cache,在Cronet创建过程的异同点,最终使用httpCache,只是http_cache_transaction.cc 与 http_network_transaction.cc里面状态机的异同。

这里有个新问题可以一起探究一下,为啥Cronet的post请求不支持httpCache

首先我们看一下在http_cache_transaction中,已经有了针对post请求的单独处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// http_cache_transaction.cc
// 由 HttpCache::Transaction::DoGetBackendComplete调用
// 返回false代表走httpcache流程
bool HttpCache::Transaction::ShouldPassThrough() {
// We may have a null disk_cache if there is an error we cannot recover from,
// like not enough disk space, or sharing violations.
if (!cache_->disk_cache_.get())
return true;
if (effective_load_flags_ & LOAD_DISABLE_CACHE)
return true;
if (request_->method == "GET" || request_->method == "HEAD")
return false;
// 如果方法为post,只有在upload_data_stream存在已经其identifier不为0的情况
if (request_->method == "POST" && request_->upload_data_stream &&
request_->upload_data_stream->identifier()) {
return false;
}
if (request_->method == "PUT" && request_->upload_data_stream)
return false;
if (request_->method == "DELETE")
return false;
return true;
}

那么代码里的upload_data_stream又是做啥的呢?

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
// UploadDataStream的构造函数,也是identifier的唯一赋值入口。
UploadDataStream::UploadDataStream(bool is_chunked, int64_t identifier)
: total_size_(0),
current_position_(0),
identifier_(identifier),
is_chunked_(is_chunked),
initialized_successfully_(false),
is_eof_(false) {
}
/**
用户层代码流程是,用户调用urlrequest的start:
实际调用了CronetUrlRequest::start->CronetUploadStream::attachNativeAdapterToRequest->jni->CronetUploadDataStream();
**/
CronetUploadDataStream::CronetUploadDataStream(Delegate* delegate, int64_t size)
: UploadDataStream(size < 0, 0), //直接默认以0为identifier的初始值创建了upload_data_stream
size_(size),
waiting_on_read_(false),
read_in_progress_(false),
waiting_on_rewind_(false),
rewind_in_progress_(false),
at_front_of_stream_(true),
delegate_(delegate),
weak_factory_(this) {}

因此,cronet本身实际上把identifier关闭了(置0),使得所有依赖该值的功能都是失效的,包括post请求使用httpcache

佐证材料

先找一个相关的资料

由http协议对httpcache的介绍来看,协议层是没有对使用httpcache的方法类型做限制的。

chromium项目论坛中的一个issue,包括cronet作者mmenke rvargas的回应。
基本观点是cronet作者不认为缓存post请求是合理的。

结论

目前cronet中的httpcache是不支持post的,原因是原作者不认为这是一个合理的使用场景。