diff --git a/core/lib/Drupal/Core/Cache/Exception/CacheableAccessDeniedHttpException.php b/core/lib/Drupal/Core/Cache/Exception/CacheableAccessDeniedHttpException.php new file mode 100644 index 0000000..49750ec --- /dev/null +++ b/core/lib/Drupal/Core/Cache/Exception/CacheableAccessDeniedHttpException.php @@ -0,0 +1,23 @@ +getEntityType()->getListCacheTags(); - if ($this->hasLinkTemplate('canonical')) { - // Creating or updating an entity may change a cached 403 or 404 response. - $tags = Cache::mergeTags($tags, ['4xx-response']); - } + // Creating or updating an entity may change a cached 403 or 404 response. + $tags = Cache::mergeTags($tags, ['4xx-response']); if ($update) { // An existing entity was updated, also invalidate its unique cache tag. $tags = Cache::mergeTags($tags, $this->getCacheTagsToInvalidate()); diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php index 62f5486..3ce3ef2 100644 --- a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php @@ -5,6 +5,8 @@ use Drupal\Core\Authentication\AuthenticationProviderFilterInterface; use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface; use Drupal\Core\Authentication\AuthenticationProviderInterface; +use Drupal\Core\Cache\Exception\CacheableAccessDeniedHttpException; +use Drupal\Core\Cache\Exception\CacheableHttpExceptionInterface; use Drupal\Core\Session\AccountProxyInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -96,7 +98,7 @@ public function onKernelRequestFilterProvider(GetResponseEvent $event) { if (isset($this->filter) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) { $request = $event->getRequest(); if ($this->authenticationProvider->applies($request) && !$this->filter->appliesToRoutedRequest($request, TRUE)) { - throw new AccessDeniedHttpException(); + throw new CacheableAccessDeniedHttpException(); } } } @@ -118,6 +120,9 @@ public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) { if ($exception instanceof AccessDeniedHttpException && !$this->authenticationProvider->applies($request) && (!isset($this->filter) || $this->filter->appliesToRoutedRequest($request, FALSE))) { $challenge_exception = $this->challengeProvider->challengeException($request, $exception); if ($challenge_exception) { + if ($exception instanceof CacheableHttpExceptionInterface && $challenge_exception instanceof CacheableHttpExceptionInterface) { + $challenge_exception->addCacheableDependency($exception->getCacheableMetadata()); + } $event->setException($challenge_exception); } } diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php index 38cb895..c2f770b 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php @@ -2,6 +2,7 @@ namespace Drupal\Core\EventSubscriber; +use Drupal\Core\Cache\Exception\CacheableHttpExceptionInterface; use Drupal\Core\Routing\RedirectDestinationInterface; use Drupal\Core\Utility\Error; use Psr\Log\LoggerInterface; @@ -158,6 +159,11 @@ protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $st $response->setStatusCode($status_code); } + // Persist any cacheability metadata. + if ($exception instanceof CacheableHttpExceptionInterface) { + $response->addCacheableDependency($exception->getCacheableMetadata()); + } + // Persist any special HTTP headers that were set on the exception. if ($exception instanceof HttpExceptionInterface) { $response->headers->add($exception->getHeaders()); diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php index 4737e80..4bacbbf 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php @@ -3,11 +3,13 @@ namespace Drupal\Core\EventSubscriber; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Cache\CacheableJsonResponse; +use Drupal\Core\Cache\CacheableResponse; +use Drupal\Core\Cache\Exception\CacheableHttpExceptionInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Utility\Error; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -119,7 +121,12 @@ protected function onHtml(GetResponseForExceptionEvent $event) { $content = $this->t('The website encountered an unexpected error. Please try again later.'); $content .= $message ? '

' . $message : ''; - $response = new Response($content, 500); + $response = new CacheableResponse($content, 500); + + // Persist any cacheability metadata. + if ($exception instanceof CacheableHttpExceptionInterface) { + $response->addCacheableDependency($exception->getCacheableMetadata()); + } if ($exception instanceof HttpExceptionInterface) { $response->setStatusCode($exception->getStatusCode()); @@ -151,7 +158,13 @@ protected function onJson(GetResponseForExceptionEvent $event) { $data = ['message' => sprintf('A fatal error occurred: %s', $message)]; } - $response = new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR); + $response = new CacheableJsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR); + + // Persist any cacheability metadata. + if ($exception instanceof CacheableHttpExceptionInterface) { + $response->addCacheableDependency($exception->getCacheableMetadata()); + } + if ($exception instanceof HttpExceptionInterface) { $response->setStatusCode($exception->getStatusCode()); $response->headers->add($exception->getHeaders()); @@ -170,7 +183,13 @@ protected function onFormatUnknown(GetResponseForExceptionEvent $event) { /** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|\Exception $exception */ $exception = $event->getException(); - $response = new Response($exception->getMessage(), $exception->getStatusCode(), $exception->getHeaders()); + $response = new CacheableResponse($exception->getMessage(), $exception->getStatusCode(), $exception->getHeaders()); + + // Persist any cacheability metadata. + if ($exception instanceof CacheableHttpExceptionInterface) { + $response->addCacheableDependency($exception->getCacheableMetadata()); + } + $event->setResponse($response); } diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php index 2853b98..bf27f2f 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php @@ -2,9 +2,11 @@ namespace Drupal\Core\EventSubscriber; -use Symfony\Component\HttpFoundation\JsonResponse; +use Drupal\Core\Cache\CacheableJsonResponse; +use Drupal\Core\Cache\Exception\CacheableHttpExceptionInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; /** * Default handling for JSON errors. @@ -34,8 +36,17 @@ protected static function getPriority() { * The event to process. */ public function on400(GetResponseForExceptionEvent $event) { - $response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_BAD_REQUEST); - $event->setResponse($response); + $this->setEventResponse($event, Response::HTTP_BAD_REQUEST); + } + + /** + * Handles a 401 error for JSON. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on401(GetResponseForExceptionEvent $event) { + $this->setEventResponse($event, Response::HTTP_UNAUTHORIZED); } /** @@ -45,8 +56,7 @@ public function on400(GetResponseForExceptionEvent $event) { * The event to process. */ public function on403(GetResponseForExceptionEvent $event) { - $response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_FORBIDDEN); - $event->setResponse($response); + $this->setEventResponse($event, Response::HTTP_FORBIDDEN); } /** @@ -56,8 +66,7 @@ public function on403(GetResponseForExceptionEvent $event) { * The event to process. */ public function on404(GetResponseForExceptionEvent $event) { - $response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_NOT_FOUND); - $event->setResponse($response); + $this->setEventResponse($event, Response::HTTP_NOT_FOUND); } /** @@ -67,8 +76,7 @@ public function on404(GetResponseForExceptionEvent $event) { * The event to process. */ public function on405(GetResponseForExceptionEvent $event) { - $response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_METHOD_NOT_ALLOWED); - $event->setResponse($response); + $this->setEventResponse($event, Response::HTTP_METHOD_NOT_ALLOWED); } /** @@ -78,8 +86,7 @@ public function on405(GetResponseForExceptionEvent $event) { * The event to process. */ public function on406(GetResponseForExceptionEvent $event) { - $response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_NOT_ACCEPTABLE); - $event->setResponse($response); + $this->setEventResponse($event, Response::HTTP_NOT_ACCEPTABLE); } /** @@ -89,7 +96,31 @@ public function on406(GetResponseForExceptionEvent $event) { * The event to process. */ public function on415(GetResponseForExceptionEvent $event) { - $response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_UNSUPPORTED_MEDIA_TYPE); + $this->setEventResponse($event, Response::HTTP_UNSUPPORTED_MEDIA_TYPE); + } + + /** + * Sets the Response for the exception event. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The current exception event. + * @param int $status + * The HTTP status code to set for the response. + */ + protected function setEventResponse(GetResponseForExceptionEvent $event, $status) { + $exception = $event->getException(); + $response = new CacheableJsonResponse(['message' => $exception->getMessage()], $status); + + // Persist any cacheability metadata. + if ($exception instanceof CacheableHttpExceptionInterface) { + $response->addCacheableDependency($exception->getCacheableMetadata()); + } + + // Persist any special HTTP headers that were set on the exception. + if ($exception instanceof HttpExceptionInterface) { + $response->headers->add($exception->getHeaders()); + } + $event->setResponse($response); } diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php index a800842..cb8bb2a 100644 --- a/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php +++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php @@ -2,10 +2,10 @@ namespace Drupal\Core\PathProcessor; +use Drupal\Core\Cache\Cacheable\Exception\CacheableNotFoundHttpException; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Render\BubbleableMetadata; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Processes the inbound path by resolving it to the front page if empty. @@ -40,7 +40,7 @@ public function processInbound($path, Request $request) { if (empty($path)) { // We have to return a valid path but / won't be routable and config // might be broken so stop execution. - throw new NotFoundHttpException(); + throw new CacheableNotFoundHttpException(); } } return $path; diff --git a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php index 5c3d931..a758c6d 100644 --- a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php +++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php @@ -4,9 +4,9 @@ use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\Access\AccessResultReasonInterface; +use Drupal\Core\Cache\Exception\CacheableAccessDeniedHttpException; use Drupal\Core\Session\AccountInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; use Symfony\Component\Routing\RequestContext as SymfonyRequestContext; use Symfony\Component\Routing\RequestContextAwareInterface; @@ -111,7 +111,9 @@ protected function checkAccess(Request $request) { $request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $access_result); } if (!$access_result->isAllowed()) { - throw new AccessDeniedHttpException($access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL); + $exception = new CacheableAccessDeniedHttpException($access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL); + $exception->addCacheableDependency($access_result); + throw $exception; } } diff --git a/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php index e804f2e..35733b4 100644 --- a/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php +++ b/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Routing\Enhancer; +use Drupal\Core\Cache\Exception\CacheableNotFoundHttpException; use Drupal\Core\ParamConverter\ParamConverterManagerInterface; use Drupal\Core\ParamConverter\ParamNotConvertedException; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -9,7 +10,6 @@ use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Route; @@ -77,7 +77,7 @@ protected function copyRawVariables(array $defaults) { public function onException(GetResponseForExceptionEvent $event) { $exception = $event->getException(); if ($exception instanceof ParamNotConvertedException) { - $event->setException(new NotFoundHttpException($exception->getMessage(), $exception)); + $event->setException(new CacheableNotFoundHttpException($exception->getMessage(), $exception)); } } diff --git a/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php b/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php index 8119efa..e246c35 100644 --- a/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php +++ b/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php @@ -2,8 +2,8 @@ namespace Drupal\Core\Routing; +use Drupal\Core\Cache\Exception\CacheableNotAcceptableHttpException; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -45,7 +45,7 @@ public function filter(RouteCollection $collection, Request $request) { // We do not throw a // \Symfony\Component\Routing\Exception\ResourceNotFoundException here // because we don't want to return a 404 status code, but rather a 406. - throw new NotAcceptableHttpException("No route found for the specified format $format."); + throw new CacheableNotAcceptableHttpException("No route found for the specified format $format."); } } diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php index eac482c..0e68efa 100644 --- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php @@ -5,12 +5,12 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Authentication\AuthenticationProviderInterface; use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface; +use Drupal\Core\Cache\Exception\CacheableUnauthorizedHttpException; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Flood\FloodInterface; use Drupal\user\UserAuthInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; /** * HTTP Basic authentication provider. @@ -130,7 +130,7 @@ public function challengeException(Request $request, \Exception $previous) { $challenge = SafeMarkup::format('Basic realm="@realm"', array( '@realm' => !empty($site_name) ? $site_name : 'Access restricted', )); - return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous); + return new CacheableUnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous); } } diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 55ce64a..b9a1443 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -3,6 +3,7 @@ namespace Drupal\rest\Plugin\rest\resource; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Core\Cache\CacheableAccessDeniedHttpException; use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; @@ -106,7 +107,10 @@ public static function create(ContainerInterface $container, array $configuratio public function get(EntityInterface $entity) { $entity_access = $entity->access('view', NULL, TRUE); if (!$entity_access->isAllowed()) { - throw new AccessDeniedHttpException(); + $exception = new CacheableAccessDeniedHttpException(); + $exception->addCacheableDependency($entity); + $exception->addCacheableDependency($entity_access); + throw $exception; } $response = new ResourceResponse($entity, 200); diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index 4700b2d..e7c733b 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -88,6 +88,9 @@ public function handle(RouteMatchInterface $route_match, Request $request) { $unserialized = NULL; if (!empty($received)) { $format = $request->getContentType(); + // Set the request format so an Exception is returned in the proper + // format. + $request->setRequestFormat($format); // Only allow serialization formats that are explicitly configured. If no // formats are configured allow all and hope that the serializer knows the diff --git a/core/modules/rest/src/Tests/AuthTest.php b/core/modules/rest/src/Tests/AuthTest.php index 9f4224f..1e570ac 100644 --- a/core/modules/rest/src/Tests/AuthTest.php +++ b/core/modules/rest/src/Tests/AuthTest.php @@ -34,7 +34,7 @@ public function testRead() { // Try to read the resource as an anonymous user, which should not work. $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET'); $this->assertResponse('401', 'HTTP response code is 401 when the request is not authenticated and the user is anonymous.'); - $this->assertRaw(json_encode(['message' => 'A fatal error occurred: No authentication credentials provided.'])); + $this->assertRaw(json_encode(['message' => 'No authentication credentials provided.'])); // Ensure that cURL settings/headers aren't carried over to next request. unset($this->curlHandle); diff --git a/core/modules/rest/src/Tests/NodeTest.php b/core/modules/rest/src/Tests/NodeTest.php index 95dc475..ca73e70 100644 --- a/core/modules/rest/src/Tests/NodeTest.php +++ b/core/modules/rest/src/Tests/NodeTest.php @@ -174,7 +174,7 @@ public function testInvalidBundle() { // Make sure the response is "Bad Request". $this->assertResponse(400); - $this->assertResponseBody('{"error":"\"bad_bundle_name\" is not a valid bundle type for denormalization."}'); + $this->assertResponseBody('{"message":"\\u0022bad_bundle_name\\u0022 is not a valid bundle type for denormalization."}'); } /** @@ -191,7 +191,7 @@ public function testMissingBundle() { // Make sure the response is "Bad Request". $this->assertResponse(400); - $this->assertResponseBody('{"error":"A string must be provided as a bundle value."}'); + $this->assertResponseBody('{"message":"A string must be provided as a bundle value."}'); } } diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php index 287c2df..97970a3 100644 --- a/core/modules/rest/src/Tests/UpdateTest.php +++ b/core/modules/rest/src/Tests/UpdateTest.php @@ -370,7 +370,7 @@ protected function patchEntity(EntityInterface $entity, array $read_only_fields, $this->httpRequest($url, 'PATCH', $serialized, $mime_type); $this->assertResponse(403); - $this->assertResponseBody('{"message":"Access denied on updating field \\u0027' . $field . '\\u0027."}'); + $this->assertResponseBody('{"message":"Access denied on updating field \'' . $field . '\'."}'); if ($format === 'hal_json') { // We've just tried with this read-only field, now unset it. diff --git a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php index ba91836..0f62752 100644 --- a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php @@ -2,9 +2,12 @@ namespace Drupal\serialization\EventSubscriber; +use Drupal\Core\Cache\CacheableResponse; +use Drupal\Core\Cache\Exception\CacheableHttpExceptionInterface; use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\Serializer\SerializerInterface; /** @@ -66,6 +69,16 @@ public function on400(GetResponseForExceptionEvent $event) { } /** + * Handles a 401 error for HTTP. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on401(GetResponseForExceptionEvent $event) { + $this->setEventResponse($event, Response::HTTP_UNAUTHORIZED); + } + + /** * Handles a 403 error for HTTP. * * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event @@ -134,10 +147,22 @@ public function on429(GetResponseForExceptionEvent $event) { * The HTTP status code to set for the response. */ protected function setEventResponse(GetResponseForExceptionEvent $event, $status) { + $exception = $event->getException(); $format = $event->getRequest()->getRequestFormat(); - $content = ['message' => $event->getException()->getMessage()]; + $content = ['message' => $exception->getMessage()]; $encoded_content = $this->serializer->serialize($content, $format); - $response = new Response($encoded_content, $status); + $response = new CacheableResponse($encoded_content, $status); + + // Persist any cacheability metadata. + if ($exception instanceof CacheableHttpExceptionInterface) { + $response->addCacheableDependency($exception->getCacheableMetadata()); + } + + // Persist any special HTTP headers that were set on the exception. + if ($exception instanceof HttpExceptionInterface) { + $response->headers->add($exception->getHeaders()); + } + $event->setResponse($response); } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php index f150e4fe..407e75d 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php @@ -392,11 +392,13 @@ public function testPostSave() { $this->cacheTagsInvalidator->expects($this->at(0)) ->method('invalidateTags') ->with(array( + '4xx-response', $this->entityTypeId . '_list', // List cache tag. )); $this->cacheTagsInvalidator->expects($this->at(1)) ->method('invalidateTags') ->with(array( + '4xx-response', $this->entityTypeId . ':' . $this->values['id'], // Own cache tag. $this->entityTypeId . '_list', // List cache tag. ));