浏览器缓存策略与服务端实现

引言

Web 应用中,网页的加载速度是至关重要的,也是衡量Web应用的重要指标。针对此,浏览器缓存是一种常用的优化技术,可以显著提高用户的访问体验和网页的响应速度

性能影响

缓存对Web应用影响颇深:

  • 减少网络请求:缓存可以存储已经下载过的资源副本,当浏览器再次请求相同的资源时,可以直接使用缓存副本,避免了网络请求的开销。这样可以减少页面加载时间和网络流量,提高网页的响应速度和性能。
  • 降低服务器负载:当浏览器使用缓存副本时,减少了服务器的负载。特别是对于大型网站和高流量的应用程序,缓存可以显著减轻服务器的压力,提高系统的可扩展性和稳定性。
  • 提高页面加载速度:缓存可以减少页面加载时间,因为浏览器不需要重新下载和解析已经缓存的资源。这对于移动设备和弱网环境尤为重要,可以提供更好的用户体验。
  • 减少带宽消耗:缓存可以减少网络流量,可以降低用户的数据使用费用。

未使用缓存:

强缓存生效:

协商缓存生效:

强缓存

判断资源是否过期,如果没有过期则使用缓存,否则重新请求。常用于静态资源。

两种实现方式:

  • Cache-Control:通过设置 Cache-Control 响应头来控制强缓存。常见的 Cache-Control 值有:

    • public:响应可以被任何缓存(包括浏览器缓存和 CDN)缓存。
    • private:响应只能被浏览器缓存缓存,不允许 CDN 缓存。
    • max-age=<seconds>:设置缓存的最大有效时间,单位为秒。
  • Expires:通过设置 Expires 响应头来控制强缓存。它是一个具体的过期日期时间,表示资源在该时间之前有效。但是,Expires 是一个绝对时间,它依赖于客户端和服务器时间的同步

区别:

  • Cache-Control 是 HTTP/1.1 中引入的缓存控制头,而 Expires 是 HTTP/1.0 中引入的缓存过期时间头。
  • Cache-Control 提供了更多的缓存控制选项和灵活性。它可以指定缓存的行为,如缓存的最大有效时间、是否允许缓存的共享等。常见的 Cache-Control 值包括 publicprivatemax-age 等。而 Expires 只能指定一个具体的过期日期时间,它是一个绝对时间,依赖于客户端和服务器时间的同步。
  • Cache-Control 支持更多的缓存控制指令,如 no-cacheno-storemust-revalidate 等,可以更细粒度地控制缓存行为。而 Expires 只能指定过期时间,没有其他控制选项。Cache-Control 的优先级高于 Expires,应该优先考虑。

协商缓存

正式请求发起前向服务器发起一个请求,用来判断当前缓存是否有效。如果有效则使用缓存,否则重新请求。

两种实现方式:

  • Last-Modified 和 If-Modified-Since:服务器在响应头中返回 Last-Modified 字段,表示资源的最后修改时间。当浏览器再次请求该资源时,会发送一个 If-Modified-Since 请求头,其中包含上次获取资源时的 Last-Modified 值。如果资源的最后修改时间与 If-Modified-Since 的值相同,服务器会返回 304 Not Modified 响应,表示资源未发生变化,浏览器可以使用缓存副本。
  • ETag 和 If-None-Match:服务器在响应头中返回 ETag 字段,表示资源的唯一标识符(通常是哈希值或版本号)。当浏览器再次请求该资源时,会发送一个 If-None-Match 请求头,其中包含上次获取资源时的 ETag 值。如果资源的 ETag 值与 If-None-Match 的值相同,服务器会返回 304 Not Modified 响应。

区别:

  • Last-Modified 是一个表示资源最后修改时间的响应头字段,由服务器在响应中发送。表示资源的最后修改时间。 ETag 是一个表示资源唯一标识符的响应头字段,由服务器生成并在响应中发送。通常是一个哈希值或版本号,用于标识资源的特定版本。
  • Last-Modified 是一个相对简单的机制,它只关注资源的最后修改时间。如果资源的最后修改时间发生变化,浏览器会重新请求整个资源。而 ETag 提供了更精确的资源识别方式,不仅考虑资源的最后修改时间,还可以考虑其他因素,如内容的哈希值。如果 ETag 值发生变化,浏览器会重新请求整个资源。
  • ETag 的优先级高于 Last-Modified。如果服务器同时返回了 ETagLast-Modified,浏览器会优先使用 ETag 进行缓存验证。
  • ETag 相对于 Last-Modified 具有更高的准确性和可靠性。因为有些情况下,资源的最后修改时间可能不准确(例如,文件复制或移动时可能丢失最后修改时间) ,而 ETag 是由服务器生成的,不依赖于文件系统的时间戳。
  • ETag 的计算成本相对较高,因为它需要服务器生成和发送一个唯一标识符。而 Last-Modified 只需要读取文件的最后修改时间即可。

最佳实践

简单实现

js 复制代码
@Controller('file')
export class FileController {
    constructor(private readonly fileService: FileService) {}

    sendFile(
        stat: fs.Stats,
        filePath: string,
        res: ServerResponse,
        eTag: string,
    ) {
        res.writeHead(200, {
            'Content-Type': 'image/jpeg',
            'Content-Length': stat.size,
            ETag: eTag,
        });
        fs.createReadStream(filePath).pipe(res);
    }


    getFileEtag(filePath: string) {
        const hash = createHash('sha1');
        hash.update(fs.readFileSync(filePath));
        return hash.digest('hex');
    }

    @Get()
    getFile(@Res() res: ServerResponse, @Headers() Headers) {
        const filePath = 'public/cat.jpg';
        try {
            const stat = fs.statSync(filePath);
            if (stat.isFile()) {
                const eTag = this.getFileEtag(filePath);
                res.setHeader('Cache-Control', 'max-age=100');
                if (Headers['if-none-match'] === eTag) {
                    res.statusCode = 304;
                    res.end();
                    return;
                }
               return this.sendFile(stat, filePath, res, eTag);
            }
            res.end('not found');
        } catch (error: any) {
            console.log(error);
            res.end('error');
        }
    }
}
相关推荐
黄尚圈圈22 分钟前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水1 小时前
简洁之道 - React Hook Form
前端
正小安3 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇6 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器