...cheableStreamedResponseEventDispatcherTrait.php | 105 +++++++++++++++++++++ .../Render/CacheableStreamedResponseInterface.php | 5 + .../big_pipe/src/Render/BigPipeResponse.php | 31 +++++- core/modules/big_pipe/src/Tests/BigPipeTest.php | 2 +- 4 files changed, 139 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Render/CacheableStreamedResponseEventDispatcherTrait.php b/core/lib/Drupal/Core/Render/CacheableStreamedResponseEventDispatcherTrait.php new file mode 100644 index 0000000..2cd820b --- /dev/null +++ b/core/lib/Drupal/Core/Render/CacheableStreamedResponseEventDispatcherTrait.php @@ -0,0 +1,105 @@ +getRequestStack(); + $event_dispatcher = $this->getEventDispatcher(); + $http_kernel = $this->getHttpKernel(); + + $fake_request = $request_stack->getMasterRequest()->duplicate(); + $request_stack->push($fake_request); + $event = new FilterResponseEvent($http_kernel, $fake_request, HttpKernelInterface::MASTER_REQUEST, $cacheable_streamed_response); + $event_dispatcher->dispatch(KernelEvents::RESPONSE, $event); + $filtered_response = $event->getResponse(); + $request_stack->pop(); + + return $filtered_response; + } + + /** + * Gets the request stack service. + * + * @return \Symfony\Component\HttpFoundation\RequestStack + */ + protected function getRequestStack() { + if (!isset($this->requestStack)) { + $this->requestStack = \Drupal::requestStack(); + } + return $this->requestStack; + } + + /** + * Gets the HTTP kernel service. + * + * @return \Symfony\Component\HttpKernel\HttpKernelInterface + */ + protected function getHttpKernel() { + if (!isset($this->httpKernel)) { + $this->httpKernel = \Drupal::service('http_kernel'); + } + return $this->httpKernel; + } + + /** + * Gets the event dispatcher service. + * + * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected function getEventDispatcher() { + if (!isset($this->eventDispatcher)) { + $this->eventDispatcher = \Drupal::service('event_dispatcher'); + } + return $this->eventDispatcher; + } + +} diff --git a/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php b/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php index ee83ba2..d521d52 100644 --- a/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php +++ b/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php @@ -15,6 +15,11 @@ /** * Returns the entire streamed response for caching, if possible. * + * The returned response has had the RESPONSE event dispatched for it. + * + * @see \Symfony\Component\HttpKernel\KernelEvents::RESPONSE + * @see \Drupal\Core\Render\CacheableStreamedResponseEventDispatcherTrait + * * @return FALSE|\Drupal\Core\Cache\CacheableResponseInterface */ public function getCacheableStreamedResponse(); diff --git a/core/modules/big_pipe/src/Render/BigPipeResponse.php b/core/modules/big_pipe/src/Render/BigPipeResponse.php index b8c3b3e..2987499 100644 --- a/core/modules/big_pipe/src/Render/BigPipeResponse.php +++ b/core/modules/big_pipe/src/Render/BigPipeResponse.php @@ -3,6 +3,7 @@ namespace Drupal\big_pipe\Render; use Drupal\Core\Render\HtmlResponse; +use Drupal\Core\Render\CacheableStreamedResponseEventDispatcherTrait; use Drupal\Core\Render\CacheableStreamedResponseInterface; /** @@ -26,6 +27,13 @@ */ class BigPipeResponse extends HtmlResponse implements CacheableStreamedResponseInterface { + use CacheableStreamedResponseEventDispatcherTrait; + + /** + * Whether the current request has a session. + * + * @var bool + */ protected $requestHasSession; /** @@ -131,6 +139,14 @@ public function sendContent() { $current_content = $this->content; $result = $this->bigPipe->sendContent($current_content, $this->getAttachments(), $this->requestHasSession); + // All BigPipe placeholders are processed, so update this response's attachments. + if (isset($this->attachments['big_pipe_placeholders'])) { + unset($this->attachments['big_pipe_placeholders']); + } + if (isset($this->attachments['big_pipe_nojs_placeholders'])) { + unset($this->attachments['big_pipe_nojs_placeholders']); + } + assert('$result === FALSE || $result instanceof \Drupal\Core\Render\HtmlResponse', 'The result of BigPipe::sendContent() is either FALSE (when using JS BigPipe placeholders) or a HtmlResponse (when using no-JS BigPipe placeholders).'); $this->finalHtmlResponse = $result; @@ -154,11 +170,20 @@ public function getCacheableStreamedResponse() { // Override content with final HTML content (with replaced placeholders). $streamed_response->setContent($this->finalHtmlResponse->getContent()); - // Add cacheability metadata and attachments for rendered placeholders. + // Add any additional cacheability metadata for rendered placeholders. $streamed_response->addCacheableDependency($this->finalHtmlResponse->getCacheableMetadata()); - $streamed_response->addAttachments($this->finalHtmlResponse->getAttachments()); - return $streamed_response; + // Overwrite with final attachments (overwrite, not add, because attachments + // need to be processed, and once the response is sent, they are processed; + // if we would not overwrite, then we'd reprocess them). + // @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface + $streamed_response->setAttachments($this->finalHtmlResponse->getAttachments()); + + // Dispatch the KernelEvents::RESPONSE event, to let those event subscribers + // do what they need to do. This is f.e. necessary for the cache tags header + // for debugging purposes and for modules that integrate with reverse + // proxies that support cache tags. + return $this->filterCacheableStreamedResponse($streamed_response); } } diff --git a/core/modules/big_pipe/src/Tests/BigPipeTest.php b/core/modules/big_pipe/src/Tests/BigPipeTest.php index 79ab886..72685d0 100644 --- a/core/modules/big_pipe/src/Tests/BigPipeTest.php +++ b/core/modules/big_pipe/src/Tests/BigPipeTest.php @@ -346,7 +346,7 @@ public function testBigPipeNoSession() { $this->assertIdentical($expectations['page cache hit cache control'], $this->drupalGetHeader('Cache-Control')); $this->assertFalse($this->drupalGetHeader('Surrogate-Control'), 'No Surrogate-Control header.'); $this->assertFalse($this->drupalGetHeader('X-Accel-Buffering'), 'No X-Accel-Buffering header.'); - $this->assertNoCacheTag('cache_tag_set_in_lazy_builder'); + $this->assertCacheTag('cache_tag_set_in_lazy_builder'); // Clear the Page Cache. Note that we use a cache tag that exists on this