terminal42 / header-replay-bundle
发送用于用户上下文头部的预检请求,并重新播放以支持反向代理。
Requires
- php: ^5.6 | ^7.0
- friendsofsymfony/http-cache: ^2.1
- friendsofsymfony/http-cache-bundle: ^2.1
- symfony/options-resolver: ^3.3 | ^4.0
Requires (Dev)
README
这是什么?
缓存是任何应用程序非常重要但也很困难的任务。反向代理帮助我们维护在(或在前端)您的web服务器上的共享缓存。它们将根据您设置的缓存头(如Cache-Control
等)缓存您应用程序的响应。缓存条目始终与某个URI相关。然而,您会发现自己在尝试为同一URI拥有不同的缓存条目(也在这里考虑ESI请求)。这就是Vary
头发挥作用的地方。通过设置Vary: <header-name>
,您可以告诉反向代理为同一URI添加多个缓存条目,实际上通过不仅使用URI还包括Vary
头扩展其内部标识符。
这很好,但很多东西——尤其是在PHP应用程序中——与PHP会话绑定,因此包含PHPSESSID
cookie的Cookie
头。现在,如果您想根据会话的一部分来Vary
,则需要发送一个Vary: Cookie
响应,这基本上扼杀了代理的全部意义,因为两个用户不会拥有相同的Cookie
值,对吧?
我们需要做的是将与我们PHP会话相关的数据拆分成多个头,这样我们就可以单独对它们进行Vary
。让我们通过一个例子来探讨这个问题。想象一下一些没有响应式布局的旧应用程序,您有一些桌面和移动版本,您可以在这些版本之间切换,而应用程序会存储您在会话中喜欢的版本。现在,由于移动设备和桌面上的内容不同,我们需要单独缓存这些条目。所以,在响应上使用Page-Layout: mobile
和Page-Layout: desktop
以及相应的Vary: Page-Layout
头可以解决这个问题。但是,没有浏览器会在初始页面查看时发送您Page-Layout: mobile
头,因为它从哪里知道,反向代理因此只会考虑您的一种页面布局。太糟糕了!
当然,有解决这个问题的方法。我称之为“预检请求”,但我还没有找到它的明确定义,但概念很简单
- 客户端向服务器发送请求。
- 代理拦截请求以尝试从缓存中提供内容。
- 根据某些条件(通常是
Cookie
或Authorization
头),代理执行对真实应用的“预检请求”。 - 真实应用注意到它只是一个“预检请求”,并以“重新播放头”(在我们的例子中为
Page-Layout: <基于会话的值>
)进行回复。 - 代理“重新播放”预检请求的头,并重新开始。
- 现在,代理再次检查内部缓存,如果缓存条目想要在
Page-Layout
上进行Vary
,则正确地为同一URI提供不同版本。
您可以在位于此bundle之上的FOSHttpCacheBundle的文档中找到更多关于此概念的信息。
然而,FOSHttpCacheBundle
仅处理“用户上下文”这一个特定用例的过程。对于 FOSHttpCacheBundle
,始终只有一个头信息,即 User-Context-Hash
。当然,您可以将 Page-Layout
的值包含在这个哈希中,并且可以为每个页面布局实现单独的缓存条目。然而,这种方法的一个缺点是,它再次将多个来源的信息包含在一个头信息中。记得我们说过 Vary: Cookie
并不是很有帮助,因为没有人会共享相同的 Cookie
头信息值吗?当然,这里的情况并不完全相同,因为您仍然会有很多匹配项,但规则很简单。
您在同一头信息中包含的信息越多,您想要
Vary
的缓存命中就越少。
当您处理大量的 ESI 请求,并且您的页面由许多不同信息 Vary
的小片段组成时,这一点尤其有趣。现在想象一下,您有 10 个片段,其中只有 1 个基于用户权限(User-Context-Hash
)变化,而其他 9 个仅基于 Page-Layout
变化。如果您将所有信息包含在同一个哈希中,那么这 9 个片段就不会根据用户权限共享,尽管它们完全可以共享!
这就是这个包介入的地方。它允许您轻松注册事件监听器,并自动为 Symfony HttpCache 处理它们。不幸的是,对于 Varnish,仍然需要手动配置,我们将在后面讨论。
安装
步骤 1:下载包
打开命令行,进入您的项目目录,并执行以下命令以下载此包的最新稳定版本
$ composer require terminal42/header-replay-bundle "^1.0" php-http/guzzle6-adapter "^1.0.0"
header-replay-bundle
位于 friendsofsymfony/http-cache-bundle
之上,该包需要虚拟包 php-http/client-implementation
。您可以使用任何喜欢的客户端实现,但您必须要求一个,这就是为什么在这个例子中我们使用了 php-http/guzzle6-adapter
。您可以在这里了解更多关于 HTTPlug 的信息。
此命令需要您全局安装 Composer,如 Composer 文档的安装章节中所述。
步骤 2:启用包
然后,通过将其添加到项目 app/AppKernel.php
文件中注册的包列表中来启用该包。
<?php // app/AppKernel.php // ... class AppKernel extends Kernel { public function registerBundles() { $bundles = array( // ... new Terminal42\HeaderReplay\HeaderReplayBundle(), ); // ... } // ... }
配置
您会喜欢这一点。此包是一个零配置包,因此您不需要做任何事情。
使用方法
- 注册应该在预请求上重新播放头信息的事件监听器。以下是一些介绍中关于
Page-Layout
的示例代码
services: app.listener.header_replay.page_layout: class: AppBundle\EventListener\HeaderReplay\PageLayoutListener tags: - { name: kernel.event_listener, event: terminal42.header_replay, method: onReplay }
<?php use Terminal42\HeaderReplay\Event\HeaderReplayEvent; class PageLayoutListener { public function onReplay(HeaderReplayEvent $event) { $request = $event->getRequest(); if (null !== $request->getSession() && $request->getSession()->has('page.layout')) { $headers = $event->getHeaders(); $headers->set('Page-Layout', $request->getSession()->get('page.layout')); } } }
就是这样,您的 Page-Layout
头信息现在将自动添加到预请求中,并且您可以在响应中对其 Vary
。请记住查看有关“代理配置”的章节,看看是否还需要做更多的事情。
顺便说一下:如果您在某些情况下需要完全绕过反向代理,请将 T42-Force-No-Cache: true
头信息添加到响应中(更好的方法是使用类常量 HeaderReplayListener::FORCE_NO_CACHE_HEADER_NAME
),就这样。不过,反向代理必须支持这一点!
代理配置
Symfony HttpCache
对于Symfony HttpCache,您可以使用EventDispatchingInterface
并实现CacheInvalidation
接口。您只需要注册此bundle提供的HeaderReplaySubscriber
,其他配置将自动生效
<?php use FOS\HttpCache\SymfonyCache\CacheInvalidation; use FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Terminal42\HeaderReplay\SymfonyCache\HeaderReplaySubscriber; class AppCache extends HttpCache implements CacheInvalidation { use EventDispatchingHttpCache; public function __construct(HttpKernelInterface $kernel, $cacheDir = null) { parent::__construct($kernel, $cacheDir); $this->addSubscriber(new HeaderReplaySubscriber()); } /** * {@inheritdoc} */ public function fetch(Request $request, $catch = false) { return parent::fetch($request, $catch); } }
默认情况下,HeaderReplaySubscriber
会检查Authorization
和Cookie
头。您可以通过向构造函数传递正确的$options
来调整此设置
<?php $subscriber = new HeaderReplaySubscriber(['user_context_headers' => ['My-Header']]);
对于Cookie
的特殊情况,如果您知道某些cookie的前置请求是无效的,您也可以忽略这些cookie。此选项接受一个正则表达式数组。例如,如果您想忽略名为Foobar
的cookie,请看下面的示例
<?php $subscriber = new HeaderReplaySubscriber([ 'ignore_cookies' => ['/^Foobar$/'] ]);
Varnish
待办事项:请帮助改进文档。