.../Render/CacheableStreamedResponseInterface.php | 27 ---- core/modules/big_pipe/big_pipe.module | 4 +- core/modules/big_pipe/big_pipe.services.yml | 2 +- .../big_pipe/src/Controller/BigPipeController.php | 4 +- .../HtmlResponseBigPipeSubscriber.php | 30 ++--- core/modules/big_pipe/src/Render/BigPipe.php | 140 ++++++++------------- .../big_pipe/src/Render/BigPipeInterface.php | 20 +-- .../big_pipe/src/Render/BigPipeResponse.php | 98 ++------------- ...cheableStreamedResponseEventDispatcherTrait.php | 105 ---------------- .../src/Render/Placeholder/BigPipeStrategy.php | 17 +-- core/modules/big_pipe/src/Tests/BigPipeTest.php | 120 +----------------- .../BigPipeResponseAttachmentsProcessorTest.php | 3 +- .../Render/Placeholder/BigPipeStrategyTest.php | 29 +---- .../page_cache/src/StackMiddleware/PageCache.php | 66 ++-------- .../modules/page_cache/src/Tests/PageCacheTest.php | 76 ++--------- .../page_cache_form_test.info.yml | 0 .../page_cache_form_test.install | 0 .../page_cache_form_test.module | 0 .../page_cache_form_test.routing.yml | 0 .../page_cache_test/page_cache_test.info.yml | 6 - .../page_cache_test/page_cache_test.routing.yml | 27 ---- .../src/PageCacheTestCacheableStreamedResponse.php | 25 ---- .../src/PageCacheTestController.php | 31 ----- .../src/Form/TestForm.php | 0 24 files changed, 122 insertions(+), 708 deletions(-) diff --git a/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php b/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php deleted file mode 100644 index d521d52..0000000 --- a/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php +++ /dev/null @@ -1,27 +0,0 @@ -hasSession($request); $page['#cache']['contexts'][] = 'session.exists'; diff --git a/core/modules/big_pipe/big_pipe.services.yml b/core/modules/big_pipe/big_pipe.services.yml index db38b7f..ff21df3 100644 --- a/core/modules/big_pipe/big_pipe.services.yml +++ b/core/modules/big_pipe/big_pipe.services.yml @@ -3,7 +3,7 @@ services: class: Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber tags: - { name: event_subscriber } - arguments: ['@big_pipe', '@session_configuration'] + arguments: ['@big_pipe'] placeholder_strategy.big_pipe: class: Drupal\big_pipe\Render\Placeholder\BigPipeStrategy arguments: ['@session_configuration', '@request_stack', '@current_route_match'] diff --git a/core/modules/big_pipe/src/Controller/BigPipeController.php b/core/modules/big_pipe/src/Controller/BigPipeController.php index 7d1ae3e..b9f02d0 100644 --- a/core/modules/big_pipe/src/Controller/BigPipeController.php +++ b/core/modules/big_pipe/src/Controller/BigPipeController.php @@ -26,7 +26,7 @@ class BigPipeController { * location. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * Thrown when the no-JS cookie is already set. + * Thrown when the no-JS cookie is already set or when there is no session. * @throws \Symfony\Component\HttpKernel\Exception\HttpException * Thrown when the original location is missing, i.e. when no 'destination' * query argument is set. @@ -40,7 +40,7 @@ public function setNoJsCookie(Request $request) { // access when either: // - the no-JS cookie is already set: this indicates a redirect loop, since // the cookie was already set, yet the user is executing this controller; - // - there is no session, in which case BigPipe never uses JS anyway, so it + // - there is no session, in which case BigPipe is not enabled anyway, so it // is pointless to set this cookie. if ($request->cookies->has(BigPipeStrategy::NOJS_COOKIE) || $request->getSession() === NULL) { throw new AccessDeniedHttpException(); diff --git a/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php b/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php index 3d30bd4..f8cef10 100644 --- a/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php +++ b/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php @@ -5,7 +5,6 @@ use Drupal\Core\Render\HtmlResponse; use Drupal\big_pipe\Render\BigPipeInterface; use Drupal\big_pipe\Render\BigPipeResponse; -use Drupal\Core\Session\SessionConfigurationInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -27,23 +26,13 @@ class HtmlResponseBigPipeSubscriber implements EventSubscriberInterface { protected $bigPipe; /** - * The session configuration. - * - * @var \Drupal\Core\Session\SessionConfigurationInterface - */ - protected $sessionConfiguration; - - /** * Constructs a HtmlResponseBigPipeSubscriber object. * * @param \Drupal\big_pipe\Render\BigPipeInterface $big_pipe * The BigPipe service. - * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration - * The session configuration. */ - public function __construct(BigPipeInterface $big_pipe, SessionConfigurationInterface $session_configuration) { + public function __construct(BigPipeInterface $big_pipe) { $this->bigPipe = $big_pipe; - $this->sessionConfiguration = $session_configuration; } /** @@ -102,12 +91,25 @@ public function onRespond(FilterResponseEvent $event) { return; } - $big_pipe_response = new BigPipeResponse($this->sessionConfiguration->hasSession($event->getRequest()), $response); - $big_pipe_response->setBigPipeService($this->bigPipe); + $big_pipe_response = new BigPipeResponse($response); + $big_pipe_response->setBigPipeService($this->getBigPipeService($event)); $event->setResponse($big_pipe_response); } /** + * Returns the BigPipe service to use to send the current response. + * + * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event + * A response event. + * + * @return \Drupal\big_pipe\Render\BigPipeInterface + * A BigPipe service. + */ + protected function getBigPipeService(FilterResponseEvent $event) { + return $this->bigPipe; + } + + /** * {@inheritdoc} */ public static function getSubscribedEvents() { diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php index 6e8e991..348e759 100644 --- a/core/modules/big_pipe/src/Render/BigPipe.php +++ b/core/modules/big_pipe/src/Render/BigPipe.php @@ -107,9 +107,48 @@ public function __construct(RendererInterface $renderer, SessionInterface $sessi } /** + * Performs tasks before sending content (and rendering placeholders). + */ + protected function performPreSendTasks() { + // The content in the placeholders may depend on the session, and by the + // time the response is sent (see index.php), the session is already + // closed. Reopen it for the duration that we are rendering placeholders. + $this->session->start(); + } + + /** + * Performs tasks after sending content (and rendering placeholders). + */ + protected function performPostSendTasks() { + // Close the session again. + $this->session->save(); + } + + /** + * Sends a chunk. + * + * @param string|\Drupal\Core\Render\HtmlResponse $chunk + * The string or response to append. String if there's no cacheability + * metadata or attachments to merge. + */ + protected function sendChunk($chunk) { + assert(is_string($chunk) || $chunk instanceof HtmlResponse); + if ($chunk instanceof HtmlResponse) { + print $chunk->getContent(); + } + else { + print $chunk; + } + flush(); + } + + /** * {@inheritdoc} */ - public function sendContent($content, array $attachments, $request_has_session) { + public function sendContent(BigPipeResponse $response) { + $content = $response->getContent(); + $attachments = $response->getAttachments(); + // First, gather the BigPipe placeholders that must be replaced. $placeholders = isset($attachments['big_pipe_placeholders']) ? $attachments['big_pipe_placeholders'] : []; $nojs_placeholders = isset($attachments['big_pipe_nojs_placeholders']) ? $attachments['big_pipe_nojs_placeholders'] : []; @@ -121,12 +160,7 @@ public function sendContent($content, array $attachments, $request_has_session) $cumulative_assets = AttachedAssets::createFromRenderArray(['#attached' => $attachments]); $cumulative_assets->setAlreadyLoadedLibraries($attachments['library']); - if ($request_has_session) { - // The content in the placeholders may depend on the session, and by the - // time the response is sent (see index.php), the session is already - // closed. Reopen it for the duration that we are rendering placeholders. - $this->session->start(); - } + $this->performPreSendTasks(); // Find the closing tag and get the strings before and after. But be // careful to use the latest occurrence of the string "", to ensure @@ -135,18 +169,11 @@ public function sendContent($content, array $attachments, $request_has_session) $post_body = array_pop($parts); $pre_body = implode('', $parts); - $streamed_response = $this->sendPreBody($pre_body, $nojs_placeholders, $cumulative_assets); + $this->sendPreBody($pre_body, $nojs_placeholders, $cumulative_assets); $this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body, $placeholders), $cumulative_assets); - static::appendToResponse($streamed_response, $this->sendPostBody($post_body)); - - if ($request_has_session) { - // Close the session again. - $this->session->save(); - } + $this->sendPostBody($post_body); - // Only return the streamed response when the request has no session: only - // sessionless responses can be cached in reverse proxies. - return $request_has_session ? FALSE : $streamed_response; + $this->performPostSendTasks(); } /** @@ -159,18 +186,13 @@ public function sendContent($content, array $attachments, $request_has_session) * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets * The cumulative assets sent so far; to be updated while rendering no-JS * BigPipe placeholders. - * - * @return \Drupal\Core\Render\HtmlResponse - * The full streamed HTML with the cacheability metadata and attachments for - * the placeholders. */ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { // If there are no no-JS BigPipe placeholders, we can send the pre- // part of the page immediately. if (empty($no_js_placeholders)) { - print $pre_body; - flush(); - return new HtmlResponse($pre_body); + $this->sendChunk($pre_body); + return; } // Extract the scripts_bottom markup: the no-JS BigPipe placeholders that we @@ -179,7 +201,7 @@ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAss list($pre_scripts_bottom, $scripts_bottom, $post_scripts_bottom) = explode('', $pre_body, 3); $cumulative_assets_initial = clone $cumulative_assets; - $streamed_response = $this->sendNoJsPlaceholders($pre_scripts_bottom . $post_scripts_bottom, $no_js_placeholders, $cumulative_assets); + $this->sendNoJsPlaceholders($pre_scripts_bottom . $post_scripts_bottom, $no_js_placeholders, $cumulative_assets); // If additional asset libraries or drupalSettings were attached by any of // the placeholders, then we need to re-render scripts_bottom. @@ -212,11 +234,7 @@ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAss $scripts_bottom = $html_response->getContent(); } - print $scripts_bottom; - flush(); - static::appendToResponse($streamed_response, $scripts_bottom); - - return $streamed_response; + $this->sendChunk($scripts_bottom); } /** @@ -231,10 +249,6 @@ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAss * The cumulative assets sent so far; to be updated while rendering no-JS * BigPipe placeholders. * - * @return \Drupal\Core\Render\HtmlResponse - * The full streamed HTML with the cacheability metadata and attachments for - * the placeholders. - * * @throws \Exception * If an exception is thrown during the rendering of a placeholder, it is * caught to allow the other placeholders to still be replaced. But when @@ -242,8 +256,6 @@ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAss * simplify debugging. */ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { - $streamed_response = new HtmlResponse(); - // Split the HTML on every no-JS placeholder string. $prepare_for_preg_split = function ($placeholder_string) { return '(' . preg_quote($placeholder_string, '/') . ')'; @@ -263,9 +275,7 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse // between placeholders and it must be printed & flushed immediately. The // rest of the logic in the loop handles the placeholders. if (!isset($no_js_placeholders[$fragment])) { - print $fragment; - flush(); - static::appendToResponse($streamed_response, $fragment); + $this->sendChunk($fragment); continue; } @@ -273,9 +283,7 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse // this is the second occurrence, we can skip all calculations and just // send the same content. if ($placeholder_occurrences[$fragment] > 1 && isset($multi_occurrence_placeholders_content[$fragment])) { - print $multi_occurrence_placeholders_content[$fragment]; - flush(); - static::appendToResponse($streamed_response, $multi_occurrence_placeholders_content[$fragment]); + $this->sendChunk($multi_occurrence_placeholders_content[$fragment]); continue; } @@ -345,9 +353,7 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse // Send this embedded HTML response. - print $html_response->getContent(); - flush(); - static::appendToResponse($streamed_response, $html_response); + $this->sendChunk($html_response); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings also need to be tracked, so @@ -362,29 +368,6 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse $multi_occurrence_placeholders_content[$fragment] = $html_response->getContent(); } } - - return $streamed_response; - } - - /** - * Appends a chunk to a response, merges cacheability metadata & attachments. - * - * @param \Drupal\Core\Render\HtmlResponse $response - * The response to which to append. - * @param string|\Drupal\Core\Render\HtmlResponse $new_chunk - * The string or response to append. String if there's no cacheability - * metadata or attachments to merge. - */ - protected static function appendToResponse(HtmlResponse $response, $new_chunk) { - assert(is_string($new_chunk) || $new_chunk instanceof HtmlResponse); - if ($new_chunk instanceof HtmlResponse) { - $response->setContent($response->getContent() . $new_chunk->getContent()); - $response->addCacheableDependency($new_chunk->getCacheableMetadata()); - $response->addAttachments($new_chunk->getAttachments()); - } - else { - $response->setContent($response->getContent() . $new_chunk); - } } /** @@ -414,10 +397,7 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde } // Send the start signal. - print "\n"; - print static::START_SIGNAL; - print "\n"; - flush(); + $this->sendChunk("\n" . static::START_SIGNAL . "\n"); // A BigPipe response consists of a HTML response plus multiple embedded // AJAX responses. To process the attachments of those AJAX responses, we @@ -489,8 +469,7 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde $json EOF; - print $output; - flush(); + $this->sendChunk($output); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings are already sent; we don't need @@ -501,10 +480,7 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde } // Send the stop signal. - print "\n"; - print static::STOP_SIGNAL; - print "\n"; - flush(); + $this->sendChunk("\n" . static::STOP_SIGNAL . "\n"); } /** @@ -537,15 +513,9 @@ protected function filterEmbeddedResponse(Request $fake_request, Response $embed * * @param string $post_body * The HTML response's content after the closing tag. - * - * @return string - * The streamed HTML. */ protected function sendPostBody($post_body) { - print ''; - print $post_body; - flush(); - return $post_body; + $this->sendChunk('' . $post_body); } /** diff --git a/core/modules/big_pipe/src/Render/BigPipeInterface.php b/core/modules/big_pipe/src/Render/BigPipeInterface.php index 2542460..988a512 100644 --- a/core/modules/big_pipe/src/Render/BigPipeInterface.php +++ b/core/modules/big_pipe/src/Render/BigPipeInterface.php @@ -47,12 +47,6 @@ * This allows us to use both no-JS BigPipe and "classic" BigPipe in the same * response to maximize the amount of content we can send as early as possible. * - * Furthermore, requests without a session (i.e. requests that are not for - * authenticated users, nor for anonymous users with sessions), BigPipe is also - * supported, to make that first request (a Page Cache miss) faster, by - * streaming it. To avoid a potential no-JS redirect, no-session requests always - * use no-JS BigPipe. - * * Finally, a closer look at the implementation, and how it supports and reuses * existing Drupal concepts: * 1. BigPipe placeholders: 1 HtmlResponse + N embedded AjaxResponses. @@ -140,17 +134,9 @@ /** * Sends an HTML response in chunks using the BigPipe technique. * - * @param string $content - * The HTML response content to send. - * @param array $attachments - * The HTML response's attachments. - * @param bool $request_has_session - * Whether the current request has a session. - * - * @return FALSE|\Drupal\Core\Render\HtmlResponse - * If $has_session is TRUE, the full streamed HTML with the cacheability - * metadata and attachments for the placeholders, FALSE otherwise. + * @param \Drupal\big_pipe\Render\BigPipeResponse $response + * The BigPipe response to send. */ - public function sendContent($content, array $attachments, $request_has_session); + public function sendContent(BigPipeResponse $response); } diff --git a/core/modules/big_pipe/src/Render/BigPipeResponse.php b/core/modules/big_pipe/src/Render/BigPipeResponse.php index 1132fe2..ba4d5f0 100644 --- a/core/modules/big_pipe/src/Render/BigPipeResponse.php +++ b/core/modules/big_pipe/src/Render/BigPipeResponse.php @@ -2,7 +2,6 @@ namespace Drupal\big_pipe\Render; -use Drupal\Core\Render\CacheableStreamedResponseInterface; use Drupal\Core\Render\HtmlResponse; /** @@ -12,28 +11,11 @@ * it makes the content inaccessible (hidden behind a callback), which means no * middlewares are able to modify the content anymore. * - * Also note that this response object is aware of whether the request has a - * session or not: - * - a no-session response can be cached by Page Cache and other reverse proxies - * - a session response (anonymous or authenticated) cannot be cached by Page - * Cache and other reverse proxies. - * For that reason, response headers differ based on this distinction, as does - * the work done in the BigPipe service. - * * @see \Drupal\big_pipe\Render\BigPipeInterface * * @todo Will become obsolete with https://www.drupal.org/node/2577631 */ -class BigPipeResponse extends HtmlResponse implements CacheableStreamedResponseInterface { - - use CacheableStreamedResponseEventDispatcherTrait; - - /** - * Whether the current request has a session. - * - * @var bool - */ - protected $requestHasSession; +class BigPipeResponse extends HtmlResponse { /** * The BigPipe service. @@ -56,36 +38,29 @@ class BigPipeResponse extends HtmlResponse implements CacheableStreamedResponseI protected $originalHtmlResponse; /** - * The final HTML response. - * - * Contains replaced placeholders. Its cacheability metadata and attachments - * are only for the placeholders. - * - * @see \Drupal\Core\Render\StreamedResponseInterface - * @see ::getStreamedResponse() - * - * @var \Drupal\Core\Render\HtmlResponse|false - */ - protected $finalHtmlResponse; - - /** * Constructs a new BigPipeResponse. * - * @param bool $request_has_session - * Whether the current request has a session. * @param \Drupal\Core\Render\HtmlResponse $response * The original HTML response. */ - public function __construct($request_has_session, HtmlResponse $response) { + public function __construct(HtmlResponse $response) { parent::__construct('', $response->getStatusCode(), []); - $this->requestHasSession = $request_has_session; $this->originalHtmlResponse = $response; $this->populateBasedOnOriginalHtmlResponse(); } /** + * Returns the original HTML response. + * + * @return \Drupal\Core\Render\HtmlResponse + */ + public function getOriginalHtmlResponse() { + return $this->originalHtmlResponse; + } + + /** * Populates this BigPipeResponse object based on the original HTML response. */ protected function populateBasedOnOriginalHtmlResponse() { @@ -97,25 +72,15 @@ protected function populateBasedOnOriginalHtmlResponse() { ->setAttachments($this->originalHtmlResponse->getAttachments()) ->addCacheableDependency($this->originalHtmlResponse->getCacheableMetadata()); - // A BigPipe response can never be cached, because it is intended for a - // single user. - // @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1 - if ($this->requestHasSession) { - $this->setPrivate(); - } - // Inform surrogates how they should handle BigPipe responses: // - "no-store" specifies that the response should not be stored in cache; // it is only to be used for the original request - // - "max-age=N" specifies that the response can be cached by surrogates for - // up to N seconds // - "content" identifies what processing surrogates should perform on the // response before forwarding it. We send, "BigPipe/1.0", which surrogates // should not process at all, and in fact, they should not even buffer it // at all. // @see http://www.w3.org/TR/edge-arch/ - $control_directive = ($this->requestHasSession || !$this->originalHtmlResponse->isCacheable()) ? 'no-store' : ('max-age=' . $this->originalHtmlResponse->getMaxAge()); - $this->headers->set('Surrogate-Control', $control_directive . ', content="BigPipe/1.0"'); + $this->headers->set('Surrogate-Control', 'no-store, content="BigPipe/1.0"'); // Add header to support streaming on NGINX + php-fpm (nginx >= 1.5.6). $this->headers->set('X-Accel-Buffering', 'no'); @@ -135,8 +100,7 @@ public function setBigPipeService(BigPipeInterface $big_pipe) { * {@inheritdoc} */ public function sendContent() { - $current_content = $this->content; - $result = $this->bigPipe->sendContent($current_content, $this->getAttachments(), $this->requestHasSession); + $this->bigPipe->sendContent($this); // All BigPipe placeholders are processed, so update this response's attachments. if (isset($this->attachments['big_pipe_placeholders'])) { @@ -146,43 +110,7 @@ public function sendContent() { 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; - return $this; } - /** - * {@inheritdoc} - */ - public function getCacheableStreamedResponse() { - // We can only return the streamed response if it was collected. And we only - // collect it in one case, see … - if ($this->finalHtmlResponse === FALSE) { - return FALSE; - } - - // Start with the original HTML response, so we have the appropriate meta- - // data like headers, HTTP version, and so on. - $streamed_response = $this->originalHtmlResponse; - - // Override content with final HTML content (with replaced placeholders). - $streamed_response->setContent($this->finalHtmlResponse->getContent()); - - // Add any additional cacheability metadata for rendered placeholders. - $streamed_response->addCacheableDependency($this->finalHtmlResponse->getCacheableMetadata()); - - // 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/Render/CacheableStreamedResponseEventDispatcherTrait.php b/core/modules/big_pipe/src/Render/CacheableStreamedResponseEventDispatcherTrait.php deleted file mode 100644 index 9a5a073..0000000 --- a/core/modules/big_pipe/src/Render/CacheableStreamedResponseEventDispatcherTrait.php +++ /dev/null @@ -1,105 +0,0 @@ -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/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php b/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php index 04ff003..dab9f8c 100644 --- a/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php +++ b/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php @@ -117,8 +117,11 @@ public function processPlaceholders(array $placeholders) { return []; } - $has_session = $this->sessionConfiguration->hasSession($request); - return $this->doProcessPlaceholders($placeholders, $has_session); + if (!$this->sessionConfiguration->hasSession($request)) { + return []; + } + + return $this->doProcessPlaceholders($placeholders); } /** @@ -126,21 +129,13 @@ public function processPlaceholders(array $placeholders) { * * @param array $placeholders * The placeholders to process. - * @param bool $has_session * * @return array * The BigPipe placeholders. */ - protected function doProcessPlaceholders(array $placeholders, $has_session) { + protected function doProcessPlaceholders(array $placeholders) { $overridden_placeholders = []; foreach ($placeholders as $placeholder => $placeholder_elements) { - // When using BigPipe without a session, i.e. for anonymous users, never - // use JavaScript. This allows BigPipe to accelerate Page Cache misses. - if (!$has_session) { - $overridden_placeholders[$placeholder] = static::createBigPipeNoJsPlaceholder($placeholder, $placeholder_elements, static::placeholderIsAttributeSafe($placeholder)); - continue; - } - // BigPipe uses JavaScript and the DOM to find the placeholder to replace. // This means finding the placeholder to replace must be efficient. Most // placeholders are HTML, which we can find efficiently thanks to the diff --git a/core/modules/big_pipe/src/Tests/BigPipeTest.php b/core/modules/big_pipe/src/Tests/BigPipeTest.php index 43b364a..4a73614 100644 --- a/core/modules/big_pipe/src/Tests/BigPipeTest.php +++ b/core/modules/big_pipe/src/Tests/BigPipeTest.php @@ -158,7 +158,7 @@ public function testBigPipe() { // @see performMetaRefresh() $this->drupalGet(Url::fromRoute('big_pipe_test')); - $this->assertBigPipeResponseHeadersPresent('private', 'no-store'); + $this->assertBigPipeResponseHeadersPresent(); $this->assertNoCacheTag('cache_tag_set_in_lazy_builder'); $cases = $this->getTestCases(); @@ -237,7 +237,7 @@ public function testBigPipeNoJs() { $this->assertBigPipeNoJsCookieExists(TRUE); $this->drupalGet(Url::fromRoute('big_pipe_test')); - $this->assertBigPipeResponseHeadersPresent('private', 'no-store'); + $this->assertBigPipeResponseHeadersPresent(); $this->assertNoCacheTag('cache_tag_set_in_lazy_builder'); $cases = $this->getTestCases(); @@ -278,106 +278,6 @@ public function testBigPipeNoJs() { } /** - * Tests BigPipe-delivered HTML responses for requests with no session. - * - * Covers: - * - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber - * - \Drupal\big_pipe\Render\BigPipe - * - \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders() - * - * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases - */ - public function testBigPipeNoSession() { - $cases = [ - 'default' => [ - 'configured max-age' => 0, - 'cache control visibility' => 'private', - 'surrogate control directive' => 'no-store', - 'page cache hit cache control' => 'must-revalidate, no-cache, private', - ], - 'max-age=300' => [ - 'configured max-age' => 300, - 'cache control visibility' => 'public', - 'surrogate control directive' => 'max-age=300', - 'page cache hit cache control' => 'max-age=300, public', - ], - ]; - - foreach ($cases as $case => $expectations) { - $this->pass("No-session test case: $case"); - - // Simulate production. - $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save(); - $this->config('system.performance')->set('cache.page.max_age', $expectations['configured max-age'])->save(); - - $this->assertSessionCookieExists(FALSE); - $this->assertBigPipeNoJsCookieExists(FALSE); - - $this->pass('First request: Page Cache miss, streamed response by BigPipe', 'Debug'); - $this->drupalGet(Url::fromRoute('big_pipe_test')); - $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache'), 'Page cache miss.'); - $this->assertBigPipeResponseHeadersPresent($expectations['cache control visibility'], $expectations['surrogate control directive']); - $this->assertNoCacheTag('cache_tag_set_in_lazy_builder'); - $this->assertRaw('This should be marked active'); - $this->assertRaw('This should be marked inactive'); - - $cases = $this->getTestCases(FALSE); - $this->assertBigPipeNoJsPlaceholders([ - $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder => $cases['edge_case__invalid_html']->embeddedHtmlResponse, - $cases['html_attribute_value']->bigPipeNoJsPlaceholder => $cases['html_attribute_value']->embeddedHtmlResponse, - $cases['html']->bigPipeNoJsPlaceholder => NULL, - $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholder => $cases['edge_case__html_non_lazy_builder']->embeddedHtmlResponse, - $cases['exception__lazy_builder']->bigPipePlaceholderId => NULL, - $cases['exception__embedded_response']->bigPipePlaceholderId => NULL, - ]); - - $this->pass('Verifying there are no BigPipe placeholders & replacements…', 'Debug'); - $this->assertEqual('', $this->drupalGetHeader('BigPipe-Test-Placeholders')); - $this->pass('Verifying BigPipe start/stop signals are absent…', 'Debug'); - $this->assertNoRaw(BigPipe::START_SIGNAL, 'BigPipe start signal absent.'); - $this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent.'); - - $this->pass('Verifying BigPipe assets are absent…', 'Debug'); - $this->assertTrue(empty($this->getDrupalSettings()), 'drupalSettings and BigPipe asset library absent.'); - $this->assertRaw('', 'Closing body tag present.'); - - - $this->pass('Repeat request: Page Cache hit, BigPipe not involved', 'Debug'); - $this->drupalGet(Url::fromRoute('big_pipe_test')); - $this->assertIdentical('HIT', $this->drupalGetHeader('X-Drupal-Cache'), 'Page cache hit.'); - $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->assertCacheTag('cache_tag_set_in_lazy_builder'); - $this->assertRaw('This should be marked active'); - $this->assertRaw('This should be marked inactive'); - - - // Clear the Page Cache. Note that we use a cache tag that exists on this - // response, despite it being absent from both the Page Cache miss - // response (because it was streamed) and the Page Cache hit response - // (because it is identical to the original Page Cache miss response, - // minus the streaming). - Cache::invalidateTags(['cache_tag_set_in_lazy_builder']); - - - // Simulate development. - $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug'); - $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save(); - $this->drupalGet(Url::fromRoute('big_pipe_test')); - // The 'edge_case__html_exception' case throws an exception. - $this->assertRaw('The website encountered an unexpected error. Please try again later'); - $this->assertRaw('You are not allowed to say llamas are not cool!'); - $this->assertNoRaw('', 'Closing body tag absent: error occurred before then.'); - // The exception is expected. Do not interpret it as a test failure. - unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log'); - - // Clear the Page Cache. - Cache::invalidateTags(['rendered']); - } - } - - /** * Tests BigPipe with a multi-occurrence placeholder. */ public function testBigPipeMultiOccurrencePlaceholders() { @@ -413,20 +313,10 @@ public function testBigPipeMultiOccurrencePlaceholders() { $this->assertNoRaw('The count is 3.'); } - /** - * Asserts expected BigPipe response headers. - * - * @param $cache_control_visibility - * Either 'private' or 'public' (the latter only in case of no-session - * BigPipe responses). - * @param $surrogate_control_directive - * Either 'no-store' or 'max-age=N' (the latter only in case of no-session, - * cacheable BigPipe responses). - */ - protected function assertBigPipeResponseHeadersPresent($cache_control_visibility, $surrogate_control_directive) { + protected function assertBigPipeResponseHeadersPresent() { $this->pass('Verifying BigPipe response headers…', 'Debug'); - $this->assertTrue(FALSE !== strpos($this->drupalGetHeader('Cache-Control'), $cache_control_visibility), 'Cache-Control header set to "' . $cache_control_visibility . '".'); - $this->assertEqual($surrogate_control_directive . ', content="BigPipe/1.0"', $this->drupalGetHeader('Surrogate-Control')); + $this->assertTrue(FALSE !== strpos($this->drupalGetHeader('Cache-Control'), 'private'), 'Cache-Control header set to "private".'); + $this->assertEqual('no-store, content="BigPipe/1.0"', $this->drupalGetHeader('Surrogate-Control')); $this->assertEqual('no', $this->drupalGetHeader('X-Accel-Buffering')); } diff --git a/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php b/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php index a3cfa3b..f7d137d 100644 --- a/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php +++ b/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php @@ -51,8 +51,7 @@ function nonHtmlResponseProvider() { * @dataProvider attachmentsProvider */ public function testHtmlResponse(array $attachments) { - $big_pipe_response = new BigPipeResponse(TRUE, new HtmlResponse()); - $big_pipe_response->setContent('original'); + $big_pipe_response = new BigPipeResponse(new HtmlResponse('original')); $big_pipe_response->setAttachments($attachments); // This mock is the main expectation of this test: verify that the decorated diff --git a/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php b/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php index 6d43a43..f0f40ba 100644 --- a/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php +++ b/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php @@ -47,7 +47,7 @@ public function testProcessPlaceholders(array $placeholders, $method, $route_mat $big_pipe_strategy = new BigPipeStrategy($session_configuration->reveal(), $request_stack->reveal(), $route_match->reveal()); $processed_placeholders = $big_pipe_strategy->processPlaceholders($placeholders); - if ($request->isMethodSafe() && !$route_match_has_no_big_pipe_option) { + if ($request->isMethodSafe() && !$route_match_has_no_big_pipe_option && $request_has_session) { $this->assertSameSize($expected_big_pipe_placeholders, $processed_placeholders, 'BigPipe is able to deliver all placeholders.'); foreach (array_keys($placeholders) as $placeholder) { $this->assertSame($expected_big_pipe_placeholders[$placeholder], $processed_placeholders[$placeholder], "Verifying how BigPipeStrategy handles the placeholder '$placeholder'"); @@ -76,32 +76,9 @@ public function placeholdersProvider() { $cases['exception__embedded_response']->placeholder => $cases['exception__embedded_response']->placeholderRenderArray, ]; - // In the 'no session' cases, BigPipe *always* use no-JS placeholders, so - // the 'cookies:big_pipe_nojs' cache context is unnecessary. - $remove_no_js_cookie_cache_context = function (array $render_array) { - $render_array['#cache']['contexts'] = array_diff($render_array['#cache']['contexts'], ['cookies:big_pipe_nojs']); - return $render_array; - }; - return [ - '_no_big_pipe absent, no session, no-JS cookie absent: no-JS BigPipe placeholder used for HTML placeholders' => [$placeholders, 'GET', FALSE, FALSE, FALSE, [ - $cases['html']->placeholder => $remove_no_js_cookie_cache_context($cases['html']->bigPipeNoJsPlaceholderRenderArray), - $cases['html_attribute_value']->placeholder => $remove_no_js_cookie_cache_context($cases['html_attribute_value']->bigPipeNoJsPlaceholderRenderArray), - $cases['html_attribute_value_subset']->placeholder => $remove_no_js_cookie_cache_context($cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray), - $cases['edge_case__invalid_html']->placeholder => $remove_no_js_cookie_cache_context($cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray), - $cases['edge_case__html_non_lazy_builder']->placeholder => $remove_no_js_cookie_cache_context($cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholderRenderArray), - $cases['exception__lazy_builder']->placeholder => $remove_no_js_cookie_cache_context($cases['exception__lazy_builder']->bigPipeNoJsPlaceholderRenderArray), - $cases['exception__embedded_response']->placeholder => $remove_no_js_cookie_cache_context($cases['exception__embedded_response']->bigPipeNoJsPlaceholderRenderArray), - ]], - '_no_big_pipe absent, no session, no-JS cookie present: no-JS BigPipe placeholder used for HTML placeholders' => [$placeholders, 'GET', FALSE, FALSE, TRUE, [ - $cases['html']->placeholder => $remove_no_js_cookie_cache_context($cases['html']->bigPipeNoJsPlaceholderRenderArray), - $cases['html_attribute_value']->placeholder => $remove_no_js_cookie_cache_context($cases['html_attribute_value']->bigPipeNoJsPlaceholderRenderArray), - $cases['html_attribute_value_subset']->placeholder => $remove_no_js_cookie_cache_context($cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray), - $cases['edge_case__invalid_html']->placeholder => $remove_no_js_cookie_cache_context($cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray), - $cases['edge_case__html_non_lazy_builder']->placeholder => $remove_no_js_cookie_cache_context($cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholderRenderArray), - $cases['exception__lazy_builder']->placeholder => $remove_no_js_cookie_cache_context($cases['exception__lazy_builder']->bigPipeNoJsPlaceholderRenderArray), - $cases['exception__embedded_response']->placeholder => $remove_no_js_cookie_cache_context($cases['exception__embedded_response']->bigPipeNoJsPlaceholderRenderArray), - ]], + '_no_big_pipe absent, no session, no-JS cookie absent' => [$placeholders, 'GET', FALSE, FALSE, FALSE, []], + '_no_big_pipe absent, no session, no-JS cookie present' => [$placeholders, 'GET', FALSE, FALSE, TRUE, []], '_no_big_pipe present, no session, no-JS cookie absent' => [$placeholders, 'GET', TRUE, FALSE, FALSE, []], '_no_big_pipe present, no session, no-JS cookie present' => [$placeholders, 'GET', TRUE, FALSE, TRUE, []], '_no_big_pipe present, session, no-JS cookie absent' => [$placeholders, 'GET', TRUE, TRUE, FALSE, []], diff --git a/core/modules/page_cache/src/StackMiddleware/PageCache.php b/core/modules/page_cache/src/StackMiddleware/PageCache.php index c8b1fcf..a4d9053 100644 --- a/core/modules/page_cache/src/StackMiddleware/PageCache.php +++ b/core/modules/page_cache/src/StackMiddleware/PageCache.php @@ -7,19 +7,17 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\PageCache\RequestPolicyInterface; use Drupal\Core\PageCache\ResponsePolicyInterface; -use Drupal\Core\Render\CacheableStreamedResponseInterface; use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\TerminableInterface; /** * Executes the page caching before the main kernel takes over the request. */ -class PageCache implements HttpKernelInterface, TerminableInterface { +class PageCache implements HttpKernelInterface { /** * The wrapped HTTP kernel. @@ -84,24 +82,6 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = } /** - * {@inheritdoc} - */ - public function terminate(Request $request, Response $response) { - // Also cache streamed responses, if they support it. - if ($response instanceof CacheableStreamedResponseInterface) { - $streamed_response = $response->getCacheableStreamedResponse(); - if ($streamed_response !== FALSE) { - $this->storeResponse($request, $streamed_response); - } - } - - // Delegate, per https://github.com/stackphp/builder/issues/14. - if ($this->httpKernel instanceof TerminableInterface) { - $this->httpKernel->terminate($request, $response); - } - } - - /** * Sidesteps the page cache and directly forwards a request to the backend. * * @param \Symfony\Component\HttpFoundation\Request $request @@ -226,39 +206,6 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch /** @var \Symfony\Component\HttpFoundation\Response $response */ $response = $this->httpKernel->handle($request, $type, $catch); - // The storing of cacheable streamed responses is deferred to terminate(). - // The 'X-Drupal-Cache' header must be set here however, since the response - // is already sent to the client before terminate() is called. - // @see ::terminate() - if ($response instanceof CacheableStreamedResponseInterface) { - // Mark streamed response as a cache miss, but only if caching is allowed - // for this streamed response. - if ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY) { - $response->headers->set('X-Drupal-Cache', 'MISS'); - } - return $response; - } - - // Only set the 'X-Drupal-Cache' header if caching is allowed for this - // response. - if ($this->storeResponse($request, $response)) { - $response->headers->set('X-Drupal-Cache', 'MISS'); - } - - return $response; - } - - /** - * Stores a response in the page cache. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * A request object. - * @param \Symfony\Component\HttpFoundation\Response $response - * A response object that should be stored in the page cache. - * - * @returns bool - */ - protected function storeResponse(Request $request, Response $response) { // Drupal's primary cache invalidation architecture is cache tags: any // response that varies by a configuration value or data in a content // entity should have cache tags, to allow for instant cache invalidation @@ -281,7 +228,7 @@ protected function storeResponse(Request $request, Response $response) { // so by replacing/extending this middleware service or adding another // one. if (!$response instanceof CacheableResponseInterface) { - return FALSE; + return $response; } // Currently it is not possible to cache binary file or streamed responses: @@ -289,12 +236,12 @@ protected function storeResponse(Request $request, Response $response) { // Therefore exclude them, even for subclasses that implement // CacheableResponseInterface. if ($response instanceof BinaryFileResponse || $response instanceof StreamedResponse) { - return FALSE; + return $response; } // Allow policy rules to further restrict which responses to cache. if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) { - return FALSE; + return $response; } $request_time = $request->server->get('REQUEST_TIME'); @@ -326,7 +273,10 @@ protected function storeResponse(Request $request, Response $response) { $this->set($request, $response, $expire, $tags); } - return TRUE; + // Mark response as a cache miss. + $response->headers->set('X-Drupal-Cache', 'MISS'); + + return $response; } /** diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php index d393d7b..1ec9282 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTest.php @@ -24,7 +24,7 @@ class PageCacheTest extends WebTestBase { * * @var array */ - public static $modules = array('test_page_test', 'system_test', 'entity_test', 'page_cache_test'); + public static $modules = array('test_page_test', 'system_test', 'entity_test'); /** * {@inheritdoc} @@ -515,88 +515,26 @@ public function testCacheableResponseResponses() { */ public function testHead() { // GET, then HEAD. - $url_a = $this->buildUrl('system-test/set-header', [ - 'query' => [ - 'name' => 'Foo', - 'value' => 'bar' - ] - ]); - $response_body = $this->curlExec([ - CURLOPT_HTTPGET => TRUE, - CURLOPT_URL => $url_a, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_NOBODY => FALSE - ]); + $url_a = $this->buildUrl('system-test/set-header', ['query' => ['name' => 'Foo', 'value' => 'bar']]); + $response_body = $this->curlExec([CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url_a, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_NOBODY => FALSE]); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); $this->assertEqual('The following header was set: Foo: bar', $response_body); - $response_body = $this->curlExec([ - CURLOPT_HTTPGET => FALSE, - CURLOPT_URL => $url_a, - CURLOPT_CUSTOMREQUEST => 'HEAD', - CURLOPT_NOBODY => FALSE - ]); + $response_body = $this->curlExec([CURLOPT_HTTPGET => FALSE, CURLOPT_URL => $url_a, CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_NOBODY => FALSE]); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); $this->assertEqual('', $response_body); // HEAD, then GET. - $url_b = $this->buildUrl('system-test/set-header', [ - 'query' => [ - 'name' => 'Foo', - 'value' => 'baz' - ] - ]); - $response_body = $this->curlExec([ - CURLOPT_HTTPGET => FALSE, - CURLOPT_URL => $url_b, - CURLOPT_CUSTOMREQUEST => 'HEAD', - CURLOPT_NOBODY => FALSE - ]); + $url_b = $this->buildUrl('system-test/set-header', ['query' => ['name' => 'Foo', 'value' => 'baz']]); + $response_body = $this->curlExec([CURLOPT_HTTPGET => FALSE, CURLOPT_URL => $url_b, CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_NOBODY => FALSE]); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'baz', 'Custom header was sent.'); $this->assertEqual('', $response_body); - $response_body = $this->curlExec([ - CURLOPT_HTTPGET => TRUE, - CURLOPT_URL => $url_b, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_NOBODY => FALSE - ]); + $response_body = $this->curlExec([CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url_b, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_NOBODY => FALSE]); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'baz', 'Custom header was sent.'); $this->assertEqual('The following header was set: Foo: baz', $response_body); } - /** - * Tests Page Cache's support for streamed responses. - * - * @see \Drupal\page_cache\StackMiddleware\PageCache::terminate() - */ - public function testStreamedResponses() { - // Symfony streamed response: not even considered. - $this->drupalGet(Url::fromRoute('page_cache_test.symfony_streamed_response')); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); - $this->drupalGet(Url::fromRoute('page_cache_test.symfony_streamed_response')); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); - - // StreamedResponseInterface Response, with untracked stream: always cache - // miss. - $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.untracked')); - $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache')); - $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.untracked')); - $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache')); - - // StreamedResponseInterface Response, with tracked stream: cacheable. - $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked')); - $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache')); - $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked')); - $this->assertIdentical('HIT', $this->drupalGetHeader('X-Drupal-Cache')); - // Except when a response policy says it cannot be cached by PageCache, such - // as \Drupal\Core\PageCache\ResponsePolicy\DenyNoCacheRoutes. - $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked.no_cache')); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked.no_cache')); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - } - } diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.info.yml b/core/modules/page_cache/tests/modules/page_cache_form_test.info.yml similarity index 100% rename from core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.info.yml rename to core/modules/page_cache/tests/modules/page_cache_form_test.info.yml diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.install b/core/modules/page_cache/tests/modules/page_cache_form_test.install similarity index 100% rename from core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.install rename to core/modules/page_cache/tests/modules/page_cache_form_test.install diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.module b/core/modules/page_cache/tests/modules/page_cache_form_test.module similarity index 100% rename from core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.module rename to core/modules/page_cache/tests/modules/page_cache_form_test.module diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.routing.yml b/core/modules/page_cache/tests/modules/page_cache_form_test.routing.yml similarity index 100% rename from core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.routing.yml rename to core/modules/page_cache/tests/modules/page_cache_form_test.routing.yml diff --git a/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.info.yml b/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.info.yml deleted file mode 100644 index bef43eb..0000000 --- a/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.info.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: 'Page Cache Test' -type: module -description: 'Support module for the Page Cache module tests.' -core: 8.x -package: Testing -version: VERSION diff --git a/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.routing.yml b/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.routing.yml deleted file mode 100644 index d1e4baf..0000000 --- a/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.routing.yml +++ /dev/null @@ -1,27 +0,0 @@ -page_cache_test.symfony_streamed_response: - path: '/page_cache_test/symfony_streamedresponse' - defaults: - _controller: '\Drupal\page_cache_test\PageCacheTestController::symfonyStreamedResponse' - requirements: - _access: 'TRUE' - -page_cache_test.streamed_response_interface.untracked: - path: '/page_cache_test/streamed_response_interface/untracked' - defaults: - _controller: '\Drupal\page_cache_test\PageCacheTestController::streamedResponseInterfaceUntracked' - requirements: - _access: 'TRUE' -page_cache_test.streamed_response_interface.tracked: - path: '/page_cache_test/streamed_response_interface/tracked' - defaults: - _controller: '\Drupal\page_cache_test\PageCacheTestController::streamedResponseInterfaceTracked' - requirements: - _access: 'TRUE' -page_cache_test.streamed_response_interface.tracked.no_cache: - path: '/page_cache_test/streamed_response_interface/tracked/nocache' - defaults: - _controller: '\Drupal\page_cache_test\PageCacheTestController::streamedResponseInterfaceTracked' - requirements: - _access: 'TRUE' - options: - no_cache: TRUE diff --git a/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestCacheableStreamedResponse.php b/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestCacheableStreamedResponse.php deleted file mode 100644 index 3042d6a..0000000 --- a/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestCacheableStreamedResponse.php +++ /dev/null @@ -1,25 +0,0 @@ -streamedResponse = $streamed_response; - parent::__construct('', 200, []); - } - - public function getCacheableStreamedResponse() { - return $this->streamedResponse; - } -} diff --git a/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestController.php b/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestController.php deleted file mode 100644 index 0809cfe..0000000 --- a/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestController.php +++ /dev/null @@ -1,31 +0,0 @@ -setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 UTC'))); - } - -} diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test/src/Form/TestForm.php b/core/modules/page_cache/tests/modules/src/Form/TestForm.php similarity index 100% rename from core/modules/page_cache/tests/modules/page_cache_form_test/src/Form/TestForm.php rename to core/modules/page_cache/tests/modules/src/Form/TestForm.php