部署后如何强制客户端重新加载?

Posted

技术标签:

【中文标题】部署后如何强制客户端重新加载?【英文标题】:How to force client reload after deployment? 【发布时间】:2015-05-31 06:31:08 【问题描述】:

我正在使用 MEAN 堆栈(mongo、express、angular 和 node)。我相对频繁地部署到生产环境……每隔几天。我担心的是我有时会更改客户端代码和 API,我宁愿不必确保 API 与以前版本的客户端代码的向后兼容性。

在这种情况下,当我推送到生产环境时,确保所有客户端重新加载的最有效方法是什么?例如,我看到 Evernote 有一个弹出窗口,上面写着请重新加载浏览器以获取最新版本的 Evernote。我想做一些类似的事情...我是否需要沿着 socket.io 或 sock.js 的路径走下去,还是我错过了一些简单的东西并且有更简单的方法来实现这一点?

【问题讨论】:

【参考方案1】:

更新: AppCache 已于 2015 年夏季弃用,因此以下不再是最佳解决方案。新的建议是改用Service Workers。然而,Service Workers 目前仍在试验 IE 和 Safari 中的粗略(阅读:可能没有)支持。

另外,许多构建工具现在无缝地结合了缓存清除和文件“版本控制”技术来解决 OP 问题。 WebPack 可以说是这个领域的当前领导者。


这可能是使用 html5 的 AppCache 的一个很好的用例

您可能希望将其中一些步骤自动化到您的部署脚本中,但这里有一些代码可能对您有所帮助。

首先,创建您的 appcache 清单文件。这也将允许您在客户端浏览器中缓存资源,直到您明确修改 appcache 清单文件的日期。

/app.appcache:

CACHE MANIFEST

#v20150327.114142

CACHE:
/appcache.js
/an/image.jpg
/a/javascript/file.js
http://some.resource.com/a/css/file.css

NETWORK:
*
/

app.appcache 中,#v20150327.114142 行上的注释是我们向浏览器指示清单已更改并且应重新加载资源的方式。它可以是任何东西,真的,只要文件在浏览器中看起来与以前的版本不同。在应用程序中部署新代码期间,应修改此行。也可以使用构建 ID。

其次,在任何你想使用appcache的页面上,修改header标签如下:

<html manifest="/app.appcache"></html>

最后,您需要添加一些 Javascript 来检查 appcache 是否有任何更改,如果有,请对其进行处理。这是Angular module。对于这个答案,这里有一个普通的例子:

appcache.js:

window.applicationCache.addEventListener('updateready', function(e) 
    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) 
        // Browser downloaded a new app cache.
        // Swap it in and reload the page to get the latest hotness.
        window.applicationCache.swapCache();
        if (confirm('A new version of the application is available. Would you like to load it?')) 
            window.location.reload();
        
    
    else 
        // Manifest didn't changed. Don't do anything.
    
, false);

或者,如果 AppCache 不适用于您的情况,则更偏僻的解决方案是创建一个简单的 API 端点,该端点返回当前构建 ID 或上次部署日期时间。您的 Angular 应用程序偶尔会遇到此端点并将结果与​​其内部版本进行比较,如果不同,则重新加载自身。

或者,您可以考虑使用实时重新加载脚本 (example),但是,虽然对开发很有帮助,但我不确定使用实时/就地重新加载资产的想法有多好正在生产中。

【讨论】:

哇!应用缓存。我认为这对于我所追求的可能有点多……尤其是当我看到 IE9 看起来不受支持并且 Firefox 似乎需要用户同意时。不过,我真的很喜欢“贫民窟”解决方案的想法——以设定的时间间隔检查 API 端点,并在必要时调用 window.location.reload()。实际上对我来说似乎很简单......虽然我相信有人会告诉我,我可以用 socket.io 轻松地做同样的事情...... 是的;您可以使用 socket.io 来做到这一点。但这对于“实时”代码更新来说是很多开销。在超时时 ping 服务器似乎就足够了(而且资源更便宜;您不必担心打开的套接字连接的负载,也不必担心在扩展时对它们进行负载平衡..)当然,除非您的应用程序已经使用套接字.io。那么它就更有意义了。 是的 - 我也在想同样的事情......太多的开销。我们可能会在接下来的几个月内添加 socket.io,但我认为会使用 API ping 方法,直到我们真正这样做。感谢您的回答! 这可能不应该是公认的答案。在应用缓存链接的顶部:Deprecated This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time. .... Use Service Workers instead. @pmont:我不得不同意。我将通过附录相应地更新我的答案。【参考方案2】:

也许您可以将哈希添加到您的客户端代码文件名。例如app-abcd23.js. 所以浏览器将重新加载文件而不是从缓存中获取它。或者您可以将哈希添加到 url.eg app.js?hash=abcd23 但某些浏览器可能仍使用缓存版本。

我知道 rails 有 assets-pipline 来处理它,但我不熟悉 MEAN stack。为此,npm 中应该有一些包。

如果您想通知用户他们的客户端代码已过期,我认为没有必要使用socket.io。您可以在html meta tagjs 文件中定义您的版本,如果不匹配,显示一个弹出窗口并告诉用户刷新。

【讨论】:

谢谢!虽然这是一个 SPA……所以我不确定 html 元标记和 js 文件版本方法是否有效。可以想象,用户可以将浏览器窗口打开一周,然后返回并开始使用应用程序而无需重新加载,并且元标记和 js 文件版本将保持不变。文件命名点是一个很好的问题,但同样的问题 - 修复重新加载时的缓存,但如果用户不重新加载则不会。对吗? 是的,为文件名添加哈希用于浏览器缓存和 CDN。所以我认为每隔一小时或一天打一次 ajax 电话,看看是否有新版本,然后使用 js 刷新页面可能对你有用。 socket.io 仍然太多了。如果你想为你的应用添加实时的东西,我建议使用 firebase 或 parse 而不是使用 socket.io。 非常感谢您的回答!【参考方案3】:

我会先告诉你我的问题,然后我会推荐一个暂定的解决方案。我想强制我的用户注销,然后在部署生产构建时登录。在任何时候,都会有两个版本的软件部署在生产环境中。 FE 知道的软件版本和 Backend 知道的版本。大多数时候它们是一样的。在任何时候,如果它们不同步,那么我们需要重新加载客户端,让客户端知道已经推送了新的生产版本。

我假设 99.99% 的时间后端都知道在生产环境中部署的软件的最新版本。

以下是我愿意推荐的两种方法:-

    后端 API 应始终在响应标头中返回最新版本的软件。在前端,我们应该有一段通用代码来检查 API 返回的版本和 FE 上的版本是否相同。如果没有,则重新加载。

    每当用户登录时,BE 都应在 JWT 中编码最新的软件版本。并且 FE 应该继续将其作为不记名令牌与每个 API 请求一起发送。 BE 还应该为每个 API 请求编写一个通用拦截器。这将比较从 API 请求收到的 JWT 中的软件版本和

【讨论】:

【参考方案4】:
    尝试将您的 js/文件限制在较短的周期内过期,即:1 天。 但是,如果您想要弹出一些内容并告诉您的用户重新加载 (ctrl+f5) 他们的浏览器,那么只需制作一个弹出该新闻的脚本如果您只是更改了一些文件,请标记刚刚重新加载/告诉重新加载的 ip/会话,这样他们就不会因多个弹出窗口而烦恼。

【讨论】:

第 1 点是公平的。我已将静态资产在 CDN 上的到期时间从 7 天更改为 1 天。【参考方案5】:

我最近遇到了同样的问题。我通过在我的 js/css 文件中附加我的应用程序的内部版本号来解决此问题。我所有的脚本和样式标签都包含在一个通用包含文件中的脚本中,因此在 js/css 文件路径的末尾添加一个“内部版本号”很简单

/foo/bar/main.js?123

这个 123 是我在同一个头文件中记录的一个数字。每当我希望客户端强制下载应用程序的所有 js 文件时,我都会增加它。这让我可以控制何时下载新版本,但仍然允许浏览器在第一个请求之后为每个请求利用缓存。直到我通过增加内部版本号来推送另一个更新。

这也意味着我可以拥有一个任意长的缓存过期标头。

【讨论】:

【参考方案6】:

在构建过程中设置本地存储的唯一键 我正在使用 react static 并加载我自己的数据文件,每次我的内容更改时我都会在其中设置 ID

然后前端客户端从本地存储中读取密钥 (如果key不存在一定是浏览器第一次访问) 如果本地存储的密钥不匹配,则表示内容已更改 下面的火线强制重新加载

window.replace(window.location.href + '?' + key)

在我的情况下,我不得不再次运行同一行 喜欢

setTimeout( (window.replace(window.location.href + '?' + key))=>  , 1000)

完整代码如下:

const reloadIfFilesChanged = (cnt: number = 0, manifest: IManifest) => 
    try 
        // will fail if window does not exist
        if (cnt > 10) 
            return;
        
        const id = localStorage.getItem('id');
        if (!id) 
            localStorage.setItem('id', manifest.id);
         else 
            if (id !== manifest.id) 
                // manifest has changed fire reload
                // and set new id
                localStorage.setItem('id', manifest.id);
                location.replace(window.location.href + '?' + manifest.id);
                setTimeout(() => 
                    location.replace(window.location.href + '?' + manifest.id + '1');
                , 1000);
            
        
     catch (e) 
        // tslint:disable-next-line:no-parameter-reassignment
        cnt++;
        setTimeout(() => reloadIfFilesChanged(cnt, manifest), 1000);
    
;

【讨论】:

以上是关于部署后如何强制客户端重新加载?的主要内容,如果未能解决你的问题,请参考以下文章

在 Yesod 中强制 Julius 重新插值或如何避免在客户端繁重的应用程序中对服务器进行双重标记

重新部署后如何使用 Elastic Beanstalk 处理经典负载均衡器

更新后强制浏览器重新加载 Silverlight xap

如何在加载后强制 iFrame 重新加载

UIWebView 完成加载后,如何强制重新布局 UITableViewCell?

客户端IP不同,heroku中的每个页面重新加载