深入浅出Electron:原理、工程与实践
上QQ阅读APP看书,第一时间看更新

5.1 缓存策略与控制

一般情况下,我们不会使用Electron开发纯离线的客户端应用,很多静态资源和接口还是需要服务端来提供的,这些资源和一大部分接口往往不会频繁更新,几乎每次响应的结果都是相同的。如果客户端每次都希望得到服务端的响应后再渲染相应的界面,那势必会造成客户端的延迟、卡顿,同时也给服务端制造了不必要的压力。

面对这种问题,一般情况下我们会与后端的同事协商由后端提供缓存的能力,也就是在响应头中设置Cache-Control字段,当然也可以通过Expire字段控制缓存,但这个字段控制能力有限,并且属于HTTP 1.0规范下的内容,我们使用谷歌浏览器的核心,支持最新的HTTP标准,就应该首选使用Cache-Control字段了。

如果把某个资源的Cache-Control响应头设置为:max-age=3000,则表示在3000秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。

我们在开发者调试工具中查看一下这类请求的响应信息,如图5-1所示。

091-1

图5-1 响应信息

注意响应结果状态码Status Code字段被标记为200(from memory cache),说明这个资源并不是服务端响应的,而是从客户端内存取出的。还有一种情况与这种情况非常相似,那就是Status Code字段被标记为200(from disk cache),说明资源是从客户端磁盘中取出的。这就是强缓存。

至于缓存的资源到底是放置在内存中还是放置在磁盘中,不是我们决定的,是浏览器内核帮我们决定的,一般情况下体积较小的文件在客户端空闲内存较多时,会优先存放在内存中。浏览器核心加载缓存资源的策略也是优先从内存中检索,如果内存中没有检索到,才会去磁盘中查找。但对于CSS样式文件来说,大概率会被存储在磁盘上。

使用Cache-Control响应头设置缓存的完整示例如下:

cahe-control:max-age=3000,public,immutable

其中max-age已经介绍过了,public(或private)表示请求的资源是否可以被代理服务器缓存。public表示允许代理服务器缓存资源,private表示禁止代理服务器缓存资源。immutable表示该资源永不过期,就算用户强制刷新页面,浏览器也不会发起请求去服务器获取资源。

但这种缓存能力还远远不能满足现实中的需求,现在假设服务器上面的资源在3000秒内更新了,客户端在没有清除缓存也没有强制刷新的情况下,获取到的内容仍然是旧的。这就可能会造成难以预料的问题,比如请求到的是一个过期的js脚本文件,它里面的逻辑还在访问旧的服务端接口,异常就此发生。

为了处理这种情况,就需要使用协商缓存的特性,除了Cache-Control响应头外,响应头中还有另外两个属性与缓存有关,一个是Etag,另一个是Last-Modified。

Etag是被请求资源的哈希值,一旦被请求资源在服务端发生了改变,哪怕只变动了一个字符,这个值也会跟着做出改变。Last-Modified是被请求资源最后的修改时间,精确到秒。

实现协商缓存的需求,两者取其一即可,由于Etag控制更精确,所以一般开发者都使用它来完成协商缓存的需求。当然也有一些开发者为了兼容老的接口,而两者都用。

浏览器第一次请求一个资源的时候,服务器返回的header中会包含Etag或Last-Modified响应头,当浏览器再次请求该资源时,request的请求头中会包含If-None-Match或If-Modify-Since请求头,它们的值即为之前响应头中Etag或Last-Modified的值。如果两者都存在的话,大部分服务器以If-None-Match(也就是Etag)的值为准。

服务端收到这两个请求头后,会根据这两个请求头验证服务端对应的资源,以确认浏览器缓存的资源是否有效,如果有效,则服务器只返回304状态码(Not Modified),无须响应body的内容,允许浏览器使用缓存的资源。如果无效,则返回200状态码以及服务端该资源的数据,供浏览器使用。

这就是协商缓存,我想读者读到这里心里一定有一个疑惑:如果同时设置了强缓存和协商缓存,浏览器将如何处理呢?答案是以强缓存为主,强缓存未过期,直接取缓存的资源不必发起服务器请求,如果强缓存已过期,则执行协商缓存的处理策略。

如果Cache-Control的值设置为no-cache,则表明当前不存在强缓存,协商缓存不受影响。如果Cache-Control的值设置为no-store,则表明当前不存在任何缓存策略,强缓存和协商缓存均不生效。

还有一种办法是在请求地址中加入随机数参数,这种办法可以使强缓存和协商缓存都失效,因为这就相当于一个全新的请求了。

如果开发者是在调试自己的应用,可以简单地勾选调试工具中的Disable cache选项来禁用缓存,如图5-2所示。

093-1

图5-2 开发者调试工具禁用缓存

对于Electron应用来说,也可以直接删除以下目录内的所有文件,以清除缓存:

C:\Users\liuxiaolun\AppData\Roaming\yourApp\Cache

站在Electron应用架构的角度看,有些开发者可能希望自己的应用程序每个版本对应的服务端资源都是强缓存,而且永不过期。

那么可以考虑在发布这些资源的时候,把它们发布到服务端的不同目录下,最好目录就以版本号命名,且Electron发起的请求也携带上版本号,如下所示:

https:// your-domain.com/desktop/1.2.1/app-index.js

如果客户端版本没有升级,那么请求的资源将始终是这个资源。该资源的Cache-Control的过期时间就可以设置成一个相当长的时间了。当客户端升级后,版本号发生了变化,请求的资源也不再是这个资源了。所以即使是永不过期的缓存也不会影响应用的正常使用。

此策略对于那些要同时保证低版本用户和高版本用户共存的应用来说非常有用。