首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >轮询服务器从不更新更新版本的PWA。

轮询服务器从不更新更新版本的PWA。
EN

Stack Overflow用户
提问于 2020-12-17 12:24:52
回答 1查看 317关注 0票数 2

我们有一个活动版本的应用程序,它开始轮询服务器立即获得一些基本信息-当请求失败时,它会在一秒后重试,直到请求成功。

在新版本中,我们删除了这个轮询:即旧端点不再存在。

现在的问题是,PWA从不更新更新的版本:也就是说,当一个已经打开旧版本的用户打开该应用程序时,他们会看到不断尝试轮询服务器的旧版本(当然,这个请求失败了)。但是,灵活的服务工作人员从不更新新版本(即刷新不起作用,关闭浏览器选项卡,或者整个浏览器也不工作)。

注意事项:这个问题似乎与投票有关

  • 当我们有一个成功轮询服务器的应用版本(即轮询停止)时,应用程序将按预期进行更新。
  • 当我们有一个不轮询服务器的应用版本时,应用程序将按预期进行更新。
  • 但在我们的例子中,我们有一个应用程序轮询服务器和轮询不断失败(并将被重试每一秒):在这种情况下,应用程序从来没有更新!

唯一的解决办法是按下CTRL+F5来重新加载应用程序。

但这当然不是一个解决方案,因为普通用户不知道他们可以/应该这样做.

知道如何解决这个问题吗?

  • 我们需要一个可以在服务器上完成的解决方案:也就是说,不可能告诉每个用户按CTRL+F5或通过开发工具卸载应用程序等等。
  • 暂时的麻烦解决方法是重新实现端点(旧版本轮询)并返回一些有效的虚拟数据。

复制和细节

下面是一个用于重现问题的测试应用程序:https://github.com/tmtron/pwa-test

自述文件包含完整的详细信息。

初始状态:

我们以前已经启动了应用程序,浏览器有PWA的版本,它不断地轮询服务器并失败。现在我们关闭浏览器选项卡(显示应用程序)

  • 下一步:构建并提供一个新的应用程序版本
  • 当我们打开浏览器时,它仍将服务于先前的应用程序版本2(如预期的那样)。
  • 因为这是一个导航请求,浏览器将检查更新。
  • 当我们看到http-服务器的终端-几秒钟后,它将显示浏览器请求ngsw-worker.js 2020-12-17T11:03:23.948Z "GET /ngsw-worker.js“的新版本.
  • 但是它没有获得新的应用程序版本。
  • 当我们现在刷新选项卡(按F5)时,旧的应用程序版本仍然是活动的!
  • 关闭浏览器窗口并再次打开(启用计时器)无助于
    • 我们仍然看到旧版本-刷新不起作用。

NGSW状态

http://127.0.0.1:8080/ngsw/state

代码语言:javascript
复制
NGSW Debug Info:

Driver state: NORMAL ((nominal))
Latest manifest hash: b5a9f50d4efae604dbc6706040c71ecb2029c1cf
Last update check: never

=== Version b5a9f50d4efae604dbc6706040c71ecb2029c1cf ===

Clients: 4cad0ef1-179e-4629-b20f-602c4a4be1f9, 4d82d618-8fac-4e79-938c-605266702fa1, e0813c6a-3b76-4aeb-a14a-9043f0417b24, 9bf1be36-e911-4612-8158-21819eb222dc, 09337bd0-d060-46a1-b9e9-56257b879bdd, 0ce1327b-05bb-44e1-bba7-f3769cbad9b2, c9b796dd-b20c-417b-a504-7f065fd841db

=== Idle Task Queue ===
Last update tick: 1s996u
Last update run: never
Task queue:
 * init post-load (update, cleanup)
 * initialization(b5a9f50d4efae604dbc6706040c71ecb2029c1cf)
 * check-updates-on-navigation

Debug log:
  • 我不知道为什么会有这么多客户端-应用程序只在一个选项卡中打开,ngsw/state选项卡是打开的。
    • 每当您按下http://127.0.0.1:8080/ngsw/state网址上的刷新键,新客户端就会出现而不会消失。

  • 任务队列似乎从未改变过。

启动计时器的应用程序组件:

代码语言:javascript
复制
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'pwa-update-test-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  private timerId?: number;
  status = 'uninit';

  version = 1;

  timerActive = true;

  constructor(private readonly httpClient: HttpClient) {}

  ngOnInit() {
    this.status = 'waiting';
    const timerUrlParamIsPresent = window.location.href.indexOf('timer') >= 0;
    this.timerActive = timerUrlParamIsPresent
    if (this.timerActive) {
      this.timerId = window.setTimeout(() => this.getAppInfo());
    } else {
      this.status = 'timer deactivated via URL parameter';
    }
  }

  getAppInfo() {
    this.status = 'working';
    this.httpClient
      .get(window.location.origin+'/non-existing-end-point')
      .toPromise()
      .catch((err) => {
        this.status =
          new Date().toLocaleTimeString() + ' failed! (will retry soon)';
        console.log('getAppInfo failed', err);
        if (this.timerActive) {
          this.timerId = window.setTimeout(() => this.getAppInfo(), 2000);
        }
      });
  }

  stopTimer() {
    this.timerActive = false;
    clearTimeout(this.timerId);
  }
}

ngsw-config

代码语言:javascript
复制
{
  "$schema": "../../node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/manifest.webmanifest",
          "/*.css",
          "/*.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-12-19 18:43:53

发布同样的答案,我张贴了https://github.com/angular/angular/issues/40207#issuecomment-748509879的能见度。

首先,用于非常详细的复制,@tmtron❤️

它确实有助于找出问题所在。

所以,下面是发生的情况:

为了避免干扰应用程序请求,SW将不会立即检查初始化和导航请求的更新。Insted将安排一项任务,在“空闲”时检查更新情况。目前,SW被认为是空闲的,当它没有收到任何请求5秒。

因此,由于您的应用程序不断轮询(即发送请求),SW绝不会认为自己是空闲的,因此永远不会执行检查更新任务。

注意:这与请求失败或成功无关。当请求失败时,您的应用程序就会在5s空闲阈值之前发送一个新的。

尽管应用程序在失败时不断地发出相同的请求可能不是一个好主意(例如,应该有一些退避机制),但SW应该能够更优雅地处理这个问题:咧嘴笑:

例如,即使SW不被视为“空闲”,IdleScheduler也可能有最大延迟(比如30),在此之后,它执行其挂起的任务。

就工作而言,客户端可以做几件事来避免这个问题:

  1. 调用SwUpdate#checkForUpdate()以触发对更新的立即检查。这不通过IdleScheduler,因此它不受问题的影响。
  2. 对违规请求使用ngsw旁路绕过SW (这意味着重复的请求不会阻止SW被视为空闲)。
  3. 避免在请求失败时以固定速率轮询,并实现某种类型的指数退避

当然,如果您不能更新客户端代码,这些都不会对您有所帮助(因为SW一直在为旧版本服务)。

恐怕,您只能从服务器上为轮询请求返回一个2xx响应(这样应用程序就可以停止轮询,SW可以自我更新)。

或者(或另外),您可以将服务器配置为包含删除端点的清场数据头(带有"storage""*"指令),以便提出该请求的老客户端的SW将被浏览器取消注册。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65340710

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档