浏览器 的 协商缓存 与 强缓存
浏览器缓存
客户端第一次获取到资源后,根据响应的 header 内容来决定如何缓存资源,可能采用的是强缓存,也可能告诉客户端浏览器采用协商缓存。
下面用两幅图来描述浏览器的缓存是怎么玩的,让大家有个大概的认知。
浏览器第一次请求时:
浏览器后续在进行请求时:
从上图可以知道,浏览器缓存包含两种类型,即强缓存(也叫本地缓存)和协商缓存。浏览器在第一次请求发生后,再次请求时【请求地址不能变,包括查询信息search和哈希hash】,会根据第一次请求的 header 信息,判断是否使用缓存,使用哪种缓存。
两种缓存简单对比:
缓存类型 | 获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|---|
强缓存 | 从缓存取 | 200(from cache) | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(not modified) | 是,正如其名,通过服务器来告知缓存是否可用 |
两种缓存详细对比:
强缓存
浏览器通过识别 header 里的对应字段,来判断是否是强缓存,强缓存是否过期,如果是就直接从缓存中获取资源而不经过服务器,返回Status Code: 200
开启强缓存,需要设置 respone header 的相应字段:
- expires
这是http1.0时的规范;它的值为一个绝对时间的GMT格式的时间字符串,如Mon, 10 Jun 2015 21:31:12 GMT,如果发送请求的时间在expires之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源。 - cache-control
这是http1.1时出现的 header 信息。比如:cahe-control:max-age=31536000,public,immutable
- max-age:表示缓存的时间是31536000秒(1年)
- public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
- immutable表示该资源永远不变。【设置 immutable 的作用:当用户会点击浏览器左上角的刷新按钮去刷新页面,浏览器依然不会向服务器发起请求,会直接从本地磁盘或者内存中读取缓存并返回200状态
Status Code: 200 (from memory cache)
。没有设置 immutable 的话,若用户刷新了页面,即使 max-age 没有过期,浏览器也会直接去请求服务器,这就是额外的请求消耗了,这时候就相当于是走协商缓存的流程了(下面会讲到)】 - no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
- no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
- private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
注意:如果cache-control与expires同时存在的话,cache-control的优先级高于expires
协商缓存
协商缓存都是由服务器来确定缓存资源是否可用的,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问,这主要涉及到下面两组header字段,这两组搭档都是成对出现的,即第一次请求的响应头带上某个字段(Last-Modified或者Etag),则后续请求则会带上对应的请求字段(If-Modified-Since或者If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的字段。
设置协商缓存:
response header里面的设置
etag: '5c20abbd-e2e8'
last-modified: Mon, 24 Dec 2018 09:49:49 GMT
举个例子,如果服务器端是 nodejs:
res.setHeader('max-age': '3600 public')
res.setHeader(etag: '5c20abbd-e2e8')
res.setHeader('last-modified': Mon, 24 Dec 2018 09:49:49 GMT)
-
last-modified/If-Modified-Since:文件的修改时间,二者的值都是GMT格式的时间字符串,精确到秒,具体过程:
- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在 respone 的 header 加上 last-modified 的 header,这个 header 表示这个资源在服务器上的最后修改时间
- 浏览器再次跟服务器请求这个资源时,在 request 的 header上加上 If-Modified-Since 的 header,这个 header 的值就是上一次请求时返回的 last-modified 的值
- 服务器再次收到资源请求时,根据浏览器传过来 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回 304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。当服务器返回 304 Not Modified 的响应时,response header 中不会再添加 last-modified 的 header,因为既然资源没有变化,那么 last-modified 也就不会改变,这是服务器返回304时的 response header
- 浏览器收到304的响应后,就会从缓存中加载资源
- 如果协商缓存没有命中,浏览器直接从服务器加载资源时,last-modified 的 Header 在重新加载的时候会被更新,下次请求时,If-Modified-Since 会启用上次返回的 last-modified 值
-
etag/If-None-Match:服务器生成的每个文件的唯一标识,改动文件了就变了,就是个文件 hash,就像用 webpack 打包的时候,每个资源都会有这个东西,如:app.js 打包后变为 app.c20abbde.js,加个唯一 hash,也是为了解决缓存问题。其判断过程与 last-modified/If-Modified-Since 类似,与 last-modified 不一样的是,当服务器返回304 Not Modified的响应时,由于 etag 重新生成过,response header 中还会把这个 etag 返回,即使这个 etag 跟之前的没有变化。
为什么要有 etag ?
你可能会觉得使用 last-modified 已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要etag呢?HTTP1.1中 etag 的出现(也就是说,etag 是新增的,为了解决之前只有 If-Modified 的缺点)主要是为了解决几个 last-modified 比较难解决的问题:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新 get;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),if-modified-since 能检查到的粒度是秒级的,这种修改无法判断(或者说UNIX 记录 MTIME 只能精确到秒);
- 某些服务器不能精确的得到文件的最后修改时间。
这时,利用 Etag 能够更加准确的控制缓存,因为 Etag 是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符。
last-modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 last-modified,最后才决定是否返回304。
用户的行为对缓存的影响
用户操作 | expires/cache-control | last-modified/ETag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5刷新 | 无效 | 无效 |
————————————————
本文部分内容来自:
博客园「wonyun」
原文链接:https://www.cnblogs.com/wonyun/p/5524617.html
作者:小_七
链接:https://www.jianshu.com/p/9c95db596df5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原文地址:https://www.cnblogs.com/MrZhujl/p/15070866.html
- Java学习之约瑟夫环的两中处理方法
- Zabbix实现QQ报警配置
- 限制用户使用su切换身份
- 微信小程序优秀开发资源汇总
- 编译安装openresty
- hdu 3367(Pseudoforest ) (最大生成树)
- chattr命令
- hdu 1561 The more, The Better (树上背包)
- hdu 1829 A Bug's Life(分组并查集(偏移量))
- 前端游戏编程基础-如何实现Canvas图像的拖拽、点击等操作
- hdu 4081 Qin Shi Huang's National Road System (次小生成树)
- python读取系统信息
- hdu 3948 Portal (kusral+离线)
- linuxmint下pycharm创建桌面快捷方式
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 在裸机上部署Pulsar集群 顶
- JNI回调Java
- 你的函数有多快?使用 performance 监控前端性能
- spring security 密码过期强制修改密码
- Spring Boot入门系列(十五) SpringBoot开发环境热部署的配置
- idea启用springboot项目热部署
- 【LeetCode每日一题】22. Generate Parentheses
- Apache的三种工作模式
- 一个Activity向另外一个Activity传递值的方法
- 活动被系统回收,临时数据的保存方法
- C语言入门系列之5.循环控制结构程序
- 知晓当前在哪一个活动
- 一键退出程序
- C语言入门系列之7.函数的定义、参数、调用和存储类别
- Python Django开发 异常及其解决办法(二)