跳到内容

使用 Edge Side Includes

编辑此页

网关缓存是提升网站性能的绝佳方式。但它们有一个限制:它们只能缓存整个页面。如果你的页面包含动态部分,例如用户名或购物车,你可能就束手无策了。幸运的是,Symfony 为这些情况提供了一个解决方案,它基于一种名为 ESI 或 Edge Side Includes 的技术。Akamai 在 2001 年编写了这个规范,它允许页面的特定部分拥有与主页面不同的缓存策略。

ESI 规范描述了你可以嵌入到页面中以与网关缓存通信的标签。Symfony 中只实现了一个标签,include,因为这是在 Akamai 上下文之外唯一有用的标签

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
    <body>
        <!-- ... some content -->

        <!-- Embed the content of another page here -->
        <esi:include src="http://..."/>

        <!-- ... more content -->
    </body>
</html>

注意

从示例中注意到,每个 ESI 标签都需要一个完整的 URL。一个 ESI 标签代表一个可以通过给定 URL 获取的页面片段。

当处理一个请求时,网关缓存从其缓存中获取整个页面,或者从后端应用程序请求它。如果响应包含一个或多个 ESI 标签,这些标签会以相同的方式处理。换句话说,网关缓存要么从其缓存中检索包含的页面片段,要么再次从后端应用程序请求该页面片段。当所有 ESI 标签都被解析后,网关缓存将每个标签合并到主页面中,并将最终内容发送给客户端。

所有这些都在网关缓存级别透明地发生(即在你的应用程序之外)。正如你将看到的,如果你选择利用 ESI 标签,Symfony 会使包含它们的过程几乎毫不费力。

在 Symfony 中使用 ESI

首先,要使用 ESI,请确保在你的应用程序配置中启用它

1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    esi: true

现在,假设你有一个页面,它相对静态,除了内容底部的滚动新闻。使用 ESI,你可以独立于页面的其余部分缓存滚动新闻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/DefaultController.php
namespace App\Controller;

// ...
class DefaultController extends AbstractController
{
    public function about(): Response
    {
        $response = $this->render('static/about.html.twig');
        $response->setPublic();
        $response->setMaxAge(600);

        return $response;
    }
}

在这个例子中,响应被标记为 public,以使整个页面可以为所有请求缓存十分钟。接下来,通过嵌入一个 action 在模板中包含滚动新闻。这是通过 render() 助手完成的(更多细节,请参阅如何在模板中嵌入控制器)。

由于嵌入的内容来自另一个页面(或者控制器),Symfony 使用标准的 render 助手来配置 ESI 标签

1
2
3
4
5
6
7
{# templates/static/about.html.twig #}

{# you can use a controller reference #}
{{ render_esi(controller('App\\Controller\\NewsController::latest', { 'maxPerPage': 5 })) }}

{# ... or a URL #}
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}

通过使用 esi 渲染器(通过 render_esi() Twig 函数),你告诉 Symfony 该 action 应该被渲染成 ESI 标签。你可能想知道为什么你想使用助手而不是自己编写 ESI 标签。那是因为使用助手可以让你的应用程序即使在没有安装网关缓存的情况下也能工作。

提示

正如你将在下面看到的,你传递的 maxPerPage 变量可以作为参数传递给你的控制器 (即 $maxPerPage)。通过 render_esi 传递的变量也成为缓存键的一部分,这样你就可以为变量和值的每个组合拥有唯一的缓存。

当使用默认的 render() 函数(或将渲染器设置为 inline)时,Symfony 会在将响应发送给客户端之前将包含的页面内容合并到主页面中。但是,如果你使用 esi 渲染器(即调用 render_esi())并且如果 Symfony 检测到它正在与支持 ESI 的网关缓存通信,它会生成一个 ESI include 标签。但是,如果没有网关缓存,或者它不支持 ESI,Symfony 将只在主页面中合并包含的页面内容,就像你使用 render() 时一样。

注意

如果网关缓存的请求包含 Surrogate-Capability HTTP 标头,并且该标头的值在任何位置包含 ESI/1.0 字符串,Symfony 就认为该网关缓存支持 ESI。

现在,嵌入的 action 可以完全独立于主页面指定自己的缓存规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/NewsController.php
namespace App\Controller;

use Symfony\Component\HttpKernel\Attribute\Cache;
// ...

class NewsController extends AbstractController
{
    #[Cache(smaxage: 60)]
    public function latest(int $maxPerPage): Response
    {
        // ...
    }
}

在这个例子中,嵌入的 action 也被公开缓存,因为内容对于所有请求都是相同的。但是,在其他情况下,你可能需要根据你的需要使此响应成为非公开的,甚至不可缓存的。

将以上所有代码放在一起,使用 ESI,整个页面缓存将有效 600 秒,但新闻组件缓存只会持续 60 秒。

当使用控制器引用时,ESI 标签应该将嵌入的 action 引用为一个可访问的 URL,以便网关缓存可以独立于页面的其余部分获取它。Symfony 负责为任何控制器引用生成唯一的 URL,并且由于 FragmentListener 必须在你的配置中启用,因此它能够正确地路由它们

1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    fragments: { path: /_fragment }

ESI 渲染器的一个巨大优势是,你可以根据需要使你的应用程序尽可能动态,同时尽可能少地访问应用程序。

警告

片段监听器只响应签名请求。只有在使用片段渲染器和 render_esi Twig 函数时,请求才会被签名。

render_esi 助手支持其他三个有用的选项

alt
用作 ESI 标签上的 alt 属性,它允许你指定一个备用 URL,以便在找不到 src 时使用。
ignore_errors
如果设置为 true,则会在 ESI 中添加一个 onerror 属性,其值为 continue,表示如果发生故障,网关缓存将静默删除 ESI 标签。
absolute_uri
如果设置为 true,将生成一个绝对 URI。默认值:false
这项工作,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本