【看Chromium源码】之HTTP

  • 本文翻译自编程🐷的这篇文章

  • 博主自己也去看了编程🐷的相关源码(通过vscode查找)

  • Chrome源码官方目录结构

  • 代码版权所有(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
posted @ 2020-11-15 20:11  嗨Sirius  阅读(233)  评论(0编辑  收藏  举报