关于 Spartacus 服务器端渲染的 404 Not found 页面处理

132 阅读2分钟

当启动 Spartacus 时,路由由 Router 逻辑处理。将评估四种不同类型的路由:

  1. 路由应由自定义路由路径处理;客户添加了硬编码路由,并且我们应该优先考虑这些路由。
  2. 路由是 PLP(产品列表页)或 PDP(产品详情页)。
  3. 路由是 CMS(内容管理系统)内容页面。
  4. 路由是未知的(404 页面未找到)。

当向 Spartacus 提供不正确的 URL 时,它很可能属于第三种情况。Spartacus 将使用 CMS API 获取给定 URL 的内容页面。如果 CMS 找不到匹配的内容页面,它将返回 404 错误。Spartacus 将处理此 404 错误,并在幕后将用户重定向到未找到的 CMS 页面。

看个具体的例子:

我们访问这个 url:

spartacus-demo.eastus.cloudapp.azure.com/electronics…

其实就是在 category 578 后面添上一个 n

然后看到这个 404 not found 的页面:

Spartacus 试图去 CMS 查找 id 为 578n 的 CMS page,当然找不到了。然后就找 not-found CMS page,这次找到了:

如果这是第一次访问,它将进入 SSR 服务器,并且实际上会导致一些不必要的处理。这为攻击者打开了一扇门,并会增加 SSR 服务器上不必要的负载。

一种优化的方式是,在 SSR 服务器上,我们应该立即重定向到一个(静态的)404 页面。

github 上有人提出了一种解决方案

实现瑞啊的 interceptor:

                                intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
                                    if (isPlatformServer(this.platformId)) {
                                          if (request.url.includes('/cms/pages')) {
                                                  return next.handle(request).pipe(
                                                            tap({
                                                                        next: event => {
                                                                                      if (event instanceof HttpResponse &amp;&amp; event.status === 404) {
                                                                                                      this.response.status(404);
                                                                                                                    }
                                                                                                                                },
                                                                                                                                            error: err => {
                                                                                                                                                          if (err.status === 404) {
                                                                                                                                                                          this.response.status(404);
                                                                                                                                                                                        }
                                                                                                                                                                                                    }
                                                                                                                                                                                                              })
                                                                                                                                                                                                                      )
                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                
                                                                                                                                                                                                                                    return next.handle(request);
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      }" aria-label="复制" data-bs-original-title="复制"></button>
</div>
      </div><pre class="typescript hljs language-typescript"><span class="hljs-meta">@Injectable</span>()
      <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NotFoundInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">HttpInterceptor</span> {
        <span class="hljs-title function_">constructor</span>(<span class="hljs-params">
            <span class="hljs-meta">@Inject</span>(PLATFORM_ID) <span class="hljs-keyword">private</span> platformId: <span class="hljs-built_in">object</span>,
                <span class="hljs-meta">@Inject</span>(<span class="hljs-string">'response'</span>) <span class="hljs-keyword">private</span> response: <span class="hljs-built_in">any</span>,
                  </span>) { }
                  
                    <span class="hljs-title function_">intercept</span>(<span class="hljs-attr">request</span>: <span class="hljs-title class_">HttpRequest</span>&lt;<span class="hljs-built_in">any</span>&gt;, <span class="hljs-attr">next</span>: <span class="hljs-title class_">HttpHandler</span>): <span class="hljs-title class_">Observable</span>&lt;<span class="hljs-title class_">HttpEvent</span>&lt;<span class="hljs-built_in">any</span>&gt;&gt; {
                        <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isPlatformServer</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">platformId</span>)) {
                              <span class="hljs-keyword">if</span> (request.<span class="hljs-property">url</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">'/cms/pages'</span>)) {
                                      <span class="hljs-keyword">return</span> next.<span class="hljs-title function_">handle</span>(request).<span class="hljs-title function_">pipe</span>(
                                                <span class="hljs-title function_">tap</span>({
                                                            <span class="hljs-attr">next</span>: <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
                                                                          <span class="hljs-keyword">if</span> (event <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">HttpResponse</span> &amp;&amp; event.<span class="hljs-property">status</span> === <span class="hljs-number">404</span>) {
                                                                                          <span class="hljs-variable language_">this</span>.<span class="hljs-property">response</span>.<span class="hljs-title function_">status</span>(<span class="hljs-number">404</span>);
                                                                                                        }
                                                                                                                    },
                                                                                                                                <span class="hljs-attr">error</span>: <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
                                                                                                                                              <span class="hljs-keyword">if</span> (err.<span class="hljs-property">status</span> === <span class="hljs-number">404</span>) {
                                                                                                                                                              <span class="hljs-variable language_">this</span>.<span class="hljs-property">response</span>.<span class="hljs-title function_">status</span>(<span class="hljs-number">404</span>);
                                                                                                                                                                            }
                                                                                                                                                                                        }
                                                                                                                                                                                                  })
                                                                                                                                                                                                          )
                                                                                                                                                                                                                }
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                    
                                                                                                                                                                                                                        <span class="hljs-keyword">return</span> next.<span class="hljs-title function_">handle</span>(request);
                                                                                                                                                                                                                          }
                                                                                                                                                                                                                          }</pre><p>在 App module 里注册这个 interceptor:</p><div class="widget-codetool" style="display: none;">
      <div class="widget-codetool--inner">
                  <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="providers: [
                     ...
                         { provide: HTTP_INTERCEPTORS, useClass: NotFoundInterceptor, multi: true }
                           ]" aria-label="复制" data-bs-original-title="复制"></button>
</div>
      </div><pre class="typescript hljs language-typescript"><span class="hljs-attr">providers</span>: [
         ...
             { <span class="hljs-attr">provide</span>: <span class="hljs-variable constant_">HTTP_INTERCEPTORS</span>, <span class="hljs-attr">useClass</span>: <span class="hljs-title class_">NotFoundInterceptor</span>, <span class="hljs-attr">multi</span>: <span class="hljs-literal">true</span> }
               ]</pre><p>server.ts 的代码:</p><div class="widget-codetool" style="display: none;">
      <div class="widget-codetool--inner">
                  <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="server.get('*', (req, res) => {
                      res.render(indexHtml, {
                              req,
                                      providers: [
                                                { provide: APP_BASE_HREF, useValue: req.baseUrl },
                                                          { provide: 'request', useValue: req },
                                                                    { provide: 'response', useValue: res }
                                                                            ],
                                                                                  },
                                                                                        (_error, html) => {
                                                                                                if (res.get('X-Response-Status') === '404') {
                                                                                                          console.log(`[Node Express] 404 for url ${req.baseUrl}`);
                                                                                                                    res.status(404).send(html);
                                                                                                                            } else {
                                                                                                                                      // return rendered html for default
                                                                                                                                                res.send(html);
                                                                                                                                                        }
                                                                                                                                                              }
                                                                                                                                                                  );
                                                                                                                                                                    });" aria-label="复制" data-bs-original-title="复制"></button>
</div>
      </div><pre class="typescript hljs language-typescript">server.<span class="hljs-title function_">get</span>(<span class="hljs-string">'*'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
          res.<span class="hljs-title function_">render</span>(indexHtml, {
                  req,
                          <span class="hljs-attr">providers</span>: [
                                    { <span class="hljs-attr">provide</span>: <span class="hljs-variable constant_">APP_BASE_HREF</span>, <span class="hljs-attr">useValue</span>: req.<span class="hljs-property">baseUrl</span> },
                                              { <span class="hljs-attr">provide</span>: <span class="hljs-string">'request'</span>, <span class="hljs-attr">useValue</span>: req },
                                                        { <span class="hljs-attr">provide</span>: <span class="hljs-string">'response'</span>, <span class="hljs-attr">useValue</span>: res }
                                                                ],
                                                                      },
                                                                            <span class="hljs-function">(<span class="hljs-params">_error, html</span>) =&gt;</span> {
                                                                                    <span class="hljs-keyword">if</span> (res.<span class="hljs-title function_">get</span>(<span class="hljs-string">'X-Response-Status'</span>) === <span class="hljs-string">'404'</span>) {
                                                                                              <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`[Node Express] 404 for url <span class="hljs-subst">${req.baseUrl}</span>`</span>);
                                                                                                        res.<span class="hljs-title function_">status</span>(<span class="hljs-number">404</span>).<span class="hljs-title function_">send</span>(html);
                                                                                                                } <span class="hljs-keyword">else</span> {
                                                                                                                          <span class="hljs-comment">// return rendered html for default</span>
                                                                                                                                    res.<span class="hljs-title function_">send</span>(html);
                                                                                                                                            }
                                                                                                                                                  }
                                                                                                                                                      );
                                                                                                                                                        });</pre>