【看Chromium源码】之HTTP
-
本文翻译自编程🐷的这篇文章
-
博主自己也去看了编程🐷的相关源码(通过vscode查找)
-
代码版权所有(c)2012 The Chromium Authors
-
从本文开始,博主将会对前端有关的Chrome源码进行分析,毕竟计网原理和DOM构建之类的学习要是有代码实现就很好了。
HTTP报文格式
- Chrome设置UA字段
Chrome的UA字段是这么拼的:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3345.0 Safari/537.36
UA字段格式
Mozilla/5.0 ([os_info]) AppleWebKit/[webkit_major_version].[webkit_minor_version] (KHTML, like Gecko) [chrome_version] Safari/[webkit_major_version].[webkit_minor_version]
源码
(chromium/src.git-refs_tags_88.0.4322.1/content/common/user_agent.cc)
:这个文件就是想获取设备信息产品信息添加到UA字段中。
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
std::string BuildUserAgentFromOSAndProduct(const std::string& os_info,
const std::string& product) {
// Derived from Safari's UA string.
// This is done to expose our product name in a manner that is maximally
// compatible with Safari, we hope!!
std::string user_agent;
base::StringAppendF(
&user_agent,
"Mozilla/5.0 (%s) AppleWebKit/%d.%d (KHTML, like Gecko) %s Safari/%d.%d",
os_info.c_str(),
WEBKIT_VERSION_MAJOR,
WEBKIT_VERSION_MINOR,
product.c_str(),
WEBKIT_VERSION_MAJOR,
WEBKIT_VERSION_MINOR);
return user_agent;
}
- 可见Chrome获取的os信息加到user_agent,因为如果是Mac版的chromium,就要最大限度做和Safari的兼容(应该是为了使os给Safari的UA字段的权限吧,不然只写个Chrome字段可能os会不给相关权限)
HTTP缓存
强缓存
我们之前有讨论过强缓存的两种实现方式
- HTTP1.0版本,使用的是Expires过期时间
- HTTP1.1使用的是Cache-Control过期时长
在Nginx中这样设置(以后要写Nginx文档再补充):
server {
listen 80;
server_name www.rrfed.com;
# .json不要缓存,时间为0
location ~* \.sw.json$ {
expires 0;
}
# 如果是图片的话缓存30天
location ~* \.(jpg|jpeg|png|gif|webp)$ {
expires 30d;
}
# css/js缓存7天
location ~* \.(css|js)$ {
expires 7d;
}
}
- 在NodeJS中(以后写NodeJS文档再补充):
// 设置30天=2592000s缓存
response.setHeader("Cache-Control", "max-age=2592000");
那么浏览器是如何区分不同的资源进行缓存的?
- 是根据请求的URL来作为cache key值的
不是之前有说到不同的请求方式有不同的缓存方式嘛
那是怎么实现的呢:
- 以下是判断是否略过缓存的代码
- 这里有考虑到很多边界条件:
比如说disk的存储已经满了,那么就不能进行缓存了;或者是不同origin的页面也不能共享一个缓存。 - 要缓存的时候:GET和HEAD、POST和PUT的流式上传、能使缓存发生失效的DELETE和PATCH的情况下
bool HttpCache::Transaction::ShouldPassThrough() {
bool cacheable = true;
// 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()) {
cacheable = false;
} else if (effective_load_flags_ & LOAD_DISABLE_CACHE) {
cacheable = false;
}
// Prevent resources whose origin is opaque from being cached. Blink's memory
// cache should take care of reusing resources within the current page load,
// but otherwise a resource with an opaque top-frame origin won’t be used
// again. Also, if the request does not have a top frame origin, bypass the
// cache otherwise resources from different pages could share a cached entry
// in such cases.
else if (HttpCache::IsSplitCacheEnabled() &&
request_->network_isolation_key.IsTransient()) {
cacheable = false;
} else if (method_ == "GET" || method_ == "HEAD") {
} else if (method_ == "POST" && request_->upload_data_stream &&
request_->upload_data_stream->identifier()) {
} else if (method_ == "PUT" && request_->upload_data_stream) {
}
// DELETE and PATCH requests may result in invalidating the cache, so cannot
// just pass through.
else if (method_ == "DELETE" || method_ == "PATCH") {
} else {
cacheable = false;
}
NetworkIsolationKeyPresent nik_present_enum =
request_->network_isolation_key.IsFullyPopulated()
? NetworkIsolationKeyPresent::kPresent
: cacheable
? NetworkIsolationKeyPresent::kNotPresentCacheableRequest
: NetworkIsolationKeyPresent::kNotPresentNonCacheableRequest;
UMA_HISTOGRAM_ENUMERATION("HttpCache.NetworkIsolationKeyPresent2",
nik_present_enum);
return !cacheable;
}
请求完之后会对cache进行存储,通过打断点检查可以发现是放在了这个路径下
~/Library/Caches/Chromium/Default/Cache/
如下图所示:
这个目录下的缓存文件是以key值(即url)的SHA1哈希值做为文件名:
查看这个Cache目录,可以发现文件名是以哈希值加上一个0或1的后缀组成,0/1是file index(具体不深入讨论),如下图所示:
缓存文件不是把文件内容写到硬盘,而是把Chrome封装的Entry实例内存内容序列化写到硬盘,它是变量在内存的表示。如果用文本编辑器打开缓存文件是这样的:
可直接读取成相应的变量。
同时会把这个Entry放在entries_set_内存变量里面,它是一个unordered_map,即普通的哈希Map,key值就是url的sha1值,value值是一个MetaData,它保存了文件大小等几个信息,EntrySet的数据结构如下代码所示:
using EntrySet = std::unordered_map<uint64_t, EntryMetadata>;
这个entries_set_最主要的作用还是记录缓存的key值,所以它的命名是叫set而不是map。这个变量会保存它的序列化格式到硬盘,叫做索引文件index:
~/Library/Caches/Chromium/Default/Cache/index-dir/the-real-index
Chrome在启动的时候就会去加载这个文件到entries_set_里面,加载资源的时候就会先这个哈希Map里面找:
如果找得到就直接去加载硬盘文件,不去发请求了。
数据取出来之后,就会对缓存是否过期进行验证:
验证是否过期需要先计算当前的缓存的有效期,如下源码的注释:
// From RFC 2616 section 13.2.4:
//
// The max-age directive takes priority over Expires, so if max-age is present
// in a response, the calculation is simply:
//
// freshness_lifetime = max_age_value
//
// Otherwise, if Expires is present in the response, the calculation is:
//
// freshness_lifetime = expires_value - date_value
//
// Note that neither of these calculations is vulnerable to clock skew, since
// all of the information comes from the origin server.
//
// Also, if the response does have a Last-Modified time, the heuristic
// expiration value SHOULD be no more than some fraction of the interval since
// that time. A typical setting of this fraction might be 10%:
//
// freshness_lifetime = (date_value - last_modified_value) * 0.10
//
结合代码实现逻辑,这个步骤是这样的:
(1)如果给了max-age,那么有效期就是max-age指定的时间:
cache-control: max-age=10
另外如果指定了no-cache或者no-store的话,那么有效期就是0:
cache-control: no-cache
cache-control: no-store
(2)如果没有给max-age,但是给了expires,那么就使用expires指定的时间减去当前时间得到有效期:
Expires: Wed, 21 Feb 2018 07:28:00 GMT
这个日期是http-date格式,使用GMT时间。
(3)如果max-age和expires都没有,并且没有指定must-revalidate,就使用当前时间减掉last modified time乘以一个调整系数0.1做为有效期:
last-modified: Tue, 13 Feb 2018 08:16:27 GMT
如果指定了must-revalidate,如:
cache-control: max-age=10, must-revalidate
cache-control: must-revalidate
那么就不能直接使用缓存,要发个请求,如果服务返回304那么再使用缓存。
有了有效期之后再和当前的年龄进行比较,如果有效期比年龄还大则认为有效,否则无效。而这个年龄是用当前时间减掉资源响应时间,再加上一个调整时间得到:
// resident_time = now - response_time;
// current_age = corrected_initial_age + resident_time;
因为考虑到请求还需要花费时间等因素,current_age需要做一个修正。
1.获取depot_tools
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
2.获取depot_tools当前目录
pwd
3.添加环境变量
sudo vi /etc/profile 打开最后一行添加
export PATH="$PATH:/PWD/depot_tools" PWD为刚才第二步获取的路径
4.生效环境变量
source /etc/profile