浏览器缓存策略
总览
浏览器中的缓存位置、浏览器缓存应用场景、浏览器本地存储、Session和JWT
# 浏览器缓存策略
缓存如果不能用以提升性能,那么它就毫无用处
所谓浏览器的缓存,就是浏览器通过 HTTP
请求网络资源后将资源留在本地的一种行为。在页面上点击 返回和前进的按钮就是利用浏览器的缓存,回退缓存(page cache)。
- 浏览器的缓存分为两种 强缓存 和 协商缓存。
- 浏览器缓存资源的位置放置四个地方
Service Worker, Memory Cache, Disk Cache, Push Cache
。Service Worker
- 浏览器的本地缓存主要有
localStorage, sessionStorage, cookie, indexedDB
协商缓存和强缓存具体请看
# 缓存应用场景
资源预加载: 如 preloader ,preload、prefetch。preloader 与 preload 不同是资源预加载期,例如在标记化时,可能需要的 css 资源就已经被预先加载到 memory cache 中了。而资源预加载技术,通过 link 实现。可以将项目中可能用到的数据先请求过来以备页面使用。数据存放于内存缓存( memory cache)。
服务端推送: 这里是指 http2 的服务端推送,而非客户端轮询。是一种服务器根据某种规则推送客户端将可能用到的资源来减少请求时间的技术。数据存放于 push cache。推送缓存中的数据仅可以使用一次,之后将可能根据协议头存在于 http 缓存中。
Service worker: Service workers 本质上充当 客户端与服务端之间的代理服务器。这个 API 创建了有效的离线体验,它会拦截网络请求并根据网络是否可用采取来适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。server woker 的缓存不同于 http 缓存,由 server worker 自身接管,存储在 server worker,离线缓存,拓展阅读:[了解PWA]
重复的网络资源请求: 常见的网络资源请求,可以根据协议头将资源存储在硬存中,以备下一次使用(http cache),相对于内存缓存,可以进行持久化的存储,而不会局限于单次会话。
页面回退:想象有这样一种场景,你点进了一个博客,顺着博客的链接你进了另一篇文章,当你回退的时候是否会觉得上一个页面似乎很快就会退了而非重新进行了一遍加载。这就是浏览器为了浏览器性能实现的页面回退机制(Page Cache)。不过此种机制往往不存在于页面内资源寻找的过程,是一种浏览器自身不受开发者控制的实现机制。
# 浏览器中的缓存位置
强缓存命中或者协商缓存中服务器返回 304 的时候,我们直接从缓存中获取资源。那这些资源究竟缓存在什么位置呢?
浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是:
- Memory Cache
- Service Worker
- Disk Cache
- Push Cache
Service Worker
Service workers 本质上充当 客户端与服务端之间的代理服务器。这个 API 创建了有效的离线体验,它会拦截网络请求并根据网络是否可用采取来适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。server woker 的缓存不同于 http 缓存,由 server worker 自身接管,存储在 server worker
Service Worker 借鉴了 Web Worker 的 思路,即让 JS 运行在主线程之外,是浏览器的一个独立的线程,由于它脱离了浏览器的窗体,因此无法直接访问 DOM。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存、消息推送和网络代理等功能。其中的离线缓存就是 Service Worker Cache。
Service Worker 同时也是 PWA 的重要实现机制
Memory Cache指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。
Disk Cache就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。
Disk Cache (Http Cache) 这块可能有点歧义。缓存位置在磁盘,只不过使用的缓存策略(强缓存、协商缓存),需要参考 http header 的字段来确定,所以也可以叫 http cache。浏览器在这缓存的是 http 请求到的资源,而不是 http req res 本身
- 比较大的 JS、CSS 文件会直接被丢进磁盘,反之丢进内存
- 内存使用率比较高的时候,文件优先进入磁盘
Push Cache
推送缓存是 HTTP/2 中,存活在浏览器的会话session
中,存活的时间很短
# 浏览器本地存储
浏览器的本地缓存主要分为 5 种,localStorage, sessionStorage, cookie, WebSql, indexedDB
前置背景知识回顾: HTTP 协议是一种无状态协议
,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。
浏览器的本地存储主要分为Cookie
、WebStorage
和IndexedDB
,
其中WebStorage
又可以分为localStorage
和sessionStorage
。
相同点
- 存储大小均为5M左右
- 都有同源策略限制
- 仅在客户端中保存,不参与和服务器的通信
不同点
- localStorage: 存储的数据是永久性的,除非用户人为删除否则会一直存在
- sessionStorage: 与存储数据的脚本所在的标签页的有效期是相同的。一旦窗口或者标签页被关闭,那么所有通过 sessionStorage 存储的数据也会被删除。
- localStorage: 在同一个浏览器内,并且是同源窗口(协议、域名、端口一致),不同页面可以共享localStorage值
- sessionStorage: 与 localStorage 一样需要同一浏览器同源文档这一条件。不仅如此,sessionStorage 的作用域还被限定在了窗口中,也就是说,只有同一浏览器、同一窗口的同源文档才能共享数据。
为了更好的理解
sessionStorage
,我们来看个例子:例如你在浏览器中打开了两个相同地址的页面A、B,虽然这两个页面的源完全相同,但是他们还是不能共享数据,因为他们是不同窗口中的。但是如果是一个窗口中,有两个同源的
iframe
元素的话,这两个iframe
的 sessionStorage 是可以互通的。
IndexedDB
IndexedDB是运行在浏览器中的非关系型数据库
, 本质上是数据库
- 以键值对的形式存储值,包括 js 对象
- indexedDB 是异步的,存入数据不会导致页面卡顿。
- indexedDB 支持事务,事务是一系列操作过程中发生了错误,数据库会回退到操作事务之前的状态。
- 同源限制,不同源的数据库不能访问。
- 存储空间没有限制。
# cookie
Cookie 是通过浏览器将服务器返回的数据保存在本地的一小块数据(一般小于4kb)。当浏览器发送请求且浏览器存在 Cookie 时,浏览器会自动在请求头携带上 Cookie 数据。引入 Cookie 的意义是因为 HTTP 的请求是无状态的,如:浏览器发出的请求服务器没办法区分浏览器用户身份以及用户的相关操作状态(可以通过 Cookie 传递这些信息)。最开始 Cookie 被作为唯一的存储手段,但是因为浏览器的每次请求都会携带上 Cookie,会带来额外的开销,而且存储量比较小,所以后来浏览器推出了新的 Api(web stoeage Api和 indexedDb)
当接收到客户端发出的 HTTP 请求时,服务器可以发送带有响应的 Set-Cookie
标头,Cookie 通常由浏览器存储,然后将 Cookie 与 HTTP 标头一同向服务器发出请求
有两种类型的 Cookies,一种是 Session Cookies会话cookie,一种是 Persistent Cookies持久性cookie
会话 Cookie存储在内存中,永远不会写入磁盘,没有设置 Expires 和 max-Age 标示的 Cookie,关闭浏览器后就会被清空。 包含了 Expire 或 max-Age 的标志,清空的时间由其设置的时间而定视为持久性 Cookie。在到期指定的日期,Cookie 将从磁盘中删除
Domain: Cookie的域。如果设成xxx.com(一级域名),那么子域名x.xxx.com(二级域名),都可以使用xxx.com的Cookie。
Path:Cookie的路径。如果设为/,则同域名全部路径均可使用该Cookie。如果设为/xxx/,则只有路径为/xxx/可以使用该Cookie。
Cookie 同源策略
Cookie中的同源只关注域名,忽略协议和端口。所以
https://localhost:8080/ 和 http://localhost:8081/的Cookie是共享的。
跨站请求
跨站:顶级域名+二级域名 不同就跨站
跨站请求,比如说有 A、B两个网站,其中A站请求会产生 Cookie,且后续访问请求需携带回 Cookie(身份认证),如果在B网站通过链接的形式访问A站资源这个就叫跨站。这种情况访问成功与否会根据 Cookie 设置的 someSite 而定。
- someSize:None: 浏览器在同站请求、跨站请求下都会发送 Cookies
- someSize:Strict: 浏览器只会在相同站点下发送 Cookies
- someSize:Lax: 与 strict 类似,不同的是它可以从外站通过链接导航到该站。
设置 SameSite 可以阻止 跨站请求伪造攻击(CSRF)。
设置 secure 字段
标记为 secure 的 Cookie 只应通过被 Https 协议加密过的请求发送给服务端。(通过 https 创建的 Cookie 只能通过 Https 请求将 Cookie 携带到服务器,通过 http 无法拿到 Cookie
HttpOnly 的作用
会话 Cookie 中缺少 HttpOnly 属性会导致攻击者可以通过程序JS脚本document.cookie
获取到用户的 Cookie 信息,造成用户 Cookie 信息泄露,增加攻击者的跨站脚本攻击(XSS)威胁。
cookie得samesite属性默认是lax,lax模式在很多情况都是不允许跨域携带cookie。必须要把samesite设置为none,但是设置为none有一个要求,就是必须secure属性为true,也就是必须使用https。
第三方cookie
如果是你正常的正在逛着天猫,天猫会把你的信息写入一些 Cookie
到 .tmall.com
这个域下,然而打开控制台你会看到,并不是所有 Cookie
都是 .tmall.com
这个域下的,里面还有很多其他域下的 Cookie
,这些所有非当前域下的 Cookie
都属于第三方 Cookie
,虽然你可能从来没访问过这些域,但是他们已经悄悄的通过这些第三方 Cookie
来标识你的信息,然后把你的个人信息发送过去了。
# JWT
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份
JWT (JSON Web Token) 是无状态
的,因为声明被存储在客户端
,而不是服务端内存中。
身份验证可以在本地
进行,而不是在请求必须通过服务器数据库或类似位置中进行。 这意味着可以对用户进行多次身份验证,而无需与站点或应用程序的数据库进行通信,也无需在此过程中消耗大量资源。跨域认证
JWT 主要由三部分组成,每个部分用 .
进行分割,各个部分分别是
Header
它通常由两部分组成:令牌的类型(即 JWT)
和使用的签名算法
,例如 HMAC SHA256 或 RSA。{ "alg": "HS256", "typ": "JWT" }
1
2
3
4指定类型和签名算法后,Json 块被
Base64Url
编码形成 JWT 的第一部分。Payload
包含一个声明。声明是有关实体(通常是用户)和其他数据的声明。共有三种类型的声明:registered, public 和 privateregistered 声明
: 包含一组建议使用的预定义声明public 声明
:公共的声明,可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密。private 声明
:自定义声明,旨在在同意使用它们的各方之间共享信息,既不是注册声明也不是公共声明。
Signature
这个签证信息由三部分组成header (base64后的)
payload (base64后的)
secret
token 令牌,是用户身份的验证方式。 最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名)
对Token认证的五点认识
- 一个Token就是一些信息的集合;
- 在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率;
- 服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查;
- 基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端;
- 因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的;
# cookie和token都存放在header里面,有什么区别
cookie保存的session id,服务器会根据这个session id,确保服务器与客户端对话;这是的cookie是有状态的,意味着验证记录或者会话需要一直在服务端和客户端保持。而token是无状态的,服务器不记录哪些用户登录了或者哪些 JWT 被发布了,只判断token是否有效,通常我们都会给token设置有效时间,来确保不被劫持。所有劫持cookie比劫持token,更有效,毕竟cookie在某种情况下可以一直使用下去,而token不行
# Session
客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap
。Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录。session的创建与使用总是在服务端,浏览器从来都没有得到过session对象
服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束
接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。
Session 机制有个缺点,比如 A 服务器存储了 Session,就是做了负载均衡后,假如一段时间内 A 的访问量激增,会转发到 B 进行访问,但是 B 服务器并没有存储 A 的 Session,会导致 Session 的失效。
Session
的运行依赖Session ID
,而Session ID
是存在 Cookie 中的。也就是说,如果浏览器禁用了Cookie
,Session
也会失效(但是可以通过其它方式实现,比如在url
中传递Session ID
,即sid=xxxx)
# 参考
图解HTTP
图解TCP/IP
JSON Web Token 入门教程 - 阮一峰的网络日志 (ruanyifeng.com) (opens new window)
浏览器原理-浏览器缓存和本地存储篇 - 掘金 (juejin.cn) (opens new window)