当 OCC 在“/pages” API 与“/components” API 之间返回不一致的响应时,就会发生该错误。 特别是,当 OCC /pages
API 返回至少一个 CmsLink
组件(在 cms 导航中)时,OCC /components
API 并不知情(因此,当询问详细信息时,/components
API 返回空响应 此 CmsLink 组件 id),然后 Spartacus NavigationService
进入无限循环,尝试一遍又一遍地加载此 CmsLink
组件的详细信息。
考虑这种边界情况:
- 客户正在代理缓存 (Cloudflare) 后面缓存所有 OCC API
- 他们的 CMS 业务用户从“CmsNavigation”中删除了一些“CmsLink”
- 稍后发生了竞争条件:“/pages” API 的缓存尚未失效,但“/components” API 的缓存已经失效。 这意味着过时的“/pages” API 仍然返回已删除的“CmsLink”组件的 ID 作为 CMS 页面结构的一部分。 但是最新的“/components” API 在后续调用中询问该组件的详细信息,但没有返回任何数据,因为该组件不再存在(已被删除)。
- 然后,SSR 正在渲染一些页面,它调用 OCC“/pages” API,并在(陈旧缓存)响应中接收到此删除的 CmsLink 的 ID(以及通常的其他导航组件 ID)
- 然后,在同一渲染期间,SSR 调用端点“/components”,询问此“CmsLink”的详细信息,但(最新的)API 未返回此组件的任何详细信息。
然后 Spartacus 开始无限循环,试图通过“/components” API 一遍又一遍地加载这个缺失的“CmsLink”组件的详细信息。
在本地重现错误的步骤
我们需要模拟 OCC “/pages” API 返回某个 CMS 组件的 ID,但“/components” API 在被要求提供该组件 ID 时应返回空响应。 对于客户来说,发生这种情况是因为端点“/components”(早期缓存失效)与“/pages”(晚期缓存失效)之间的 OCC 缓存失效时间不同。
为了在本地重现这个错误,我们实现一个自定义的 Angular HTTP_INTERCEPTOR
就足够了,在这个 interceptor 里,我们故意让它永远不会返回有关某些特定 CMS 组件的信息。
然后打开店面并在 ChromeDevTools Network 选项卡中观察对“/components”端点的 http 调用的无限循环。
如果任何其他客户缓存 OCC 响应(出于性能原因,他们应该这样做)并随时删除 CMS 中的某些链接,则本文描述的这个错误也可能会发生。
过时的缓存“/pages” API 返回的组件可能比最新的“/components” API 返回的详细信息要多。 最终会导致挂起 NodeJS(CPU 使用率为 100%)并造成 Nginx 返回 504 Gateway Timeout 错误页面给最终用户,而不是 CSR fallback 或最终用户可以使用的任何有意义的页面。
注意:无论Spartacus运行在浏览器(CSR)还是NodeJS(SSR)中,都会发生这样的无限循环。
从 Spartacus 的角度来看,当 /pages
API 返回 cms 结构中的组件 ID 时,Spartacus 会尝试通过 /components
API 加载所有这些组件的详细信息。 目前,当“/components” API 返回给定 ID 的空响应时,ngrx 状态对象引用会被更新,尽管该空响应实际上并未更改 ngrx 状态 - 它只是状态的对象引用无缘无故地更改了 。 对象引用的此类更改会触发在此状态下可观察到的 RxJ 的 emit.
这会导致从“NavigationService.getNavigationNode()”返回的流中的“$data” Observable 对象发出一个新值。 在此流的逻辑中,Spartacus 认识到某些组件的详细信息仍然丢失,因此尝试通过调用“CmsService.loadNavigationItems(navigation.id,missingItems)”来加载它们。
这个错误已经在这个 Pull Request里修复了。