Problem/Motivation
We are using the JSON:API Resources module to define a custom JSON:API endpoint. We are setting the cacheability metadata on the resource being returned so that the max-age is set to 0. Despite this, when requests to the endpoint are accessed by anonymous users, the response is returned from the page cache unless we add the "no_cache" option to the route in routing.yml. Shouldn't the fact that the cacheability is set to a max age of 0 bubble-up to the page cache determination?
Steps to reproduce
- Install JSON:API Resources.
- Create a custom module that defines a custom JSON:API resource endpoint, following examples from the module. For example:
namespace Drupal\bug_repro\Resource; use Drupal\Component\Datetime\TimeInterface; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\jsonapi\JsonApiResource\LinkCollection; use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\JsonApiResource\ResourceObjectData; use Drupal\jsonapi\ResourceResponse; use Drupal\jsonapi\ResourceType\ResourceType; use Drupal\jsonapi_resources\Resource\ResourceBase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; class BugReproResource extends ResourceBase implements ContainerInjectionInterface { /** * The Drupal time service. * * @var \Drupal\Component\Datetime\TimeInterface */ protected $timeService; /** * {@inheritDoc} */ public static function create(ContainerInterface $container): BugReproResource { return new static($container->get('datetime.time')); } /** * Constructor for BugReproResource. * * @param \Drupal\Component\Datetime\TimeInterface $time_service * The Drupal time service. */ public function __construct(TimeInterface $time_service) { $this->timeService = $time_service; } /** * Process an bug repro query. * * @param \Symfony\Component\HttpFoundation\Request $request * The request. * * @return \Drupal\jsonapi\ResourceResponse * The response. */ public function process(Request $request): ResourceResponse { $resource_type = new ResourceType('bug_repro', 'bug_repro', NULL); // Never cache. $cacheability = (new CacheableMetadata()) ->setCacheMaxAge(0) ->addCacheContexts(['url.path', 'url.query_args']); $attributes = ['timestamp' => $this->getTimeService()->getCurrentTime()]; $primary_data = new ResourceObject( $cacheability, $resource_type, 'fake', NULL, $attributes, new LinkCollection([]) ); $top_level_data = new ResourceObjectData([$primary_data], 1); try { $response = $this->createJsonapiResponse($top_level_data, $request); } catch (InvalidPluginDefinitionException | PluginNotFoundException $ex) { throw new \RuntimeException( 'Failed to create JSON:API response: ' . $ex->getMessage(), 0, $ex ); } $response->addCacheableDependency($cacheability); return $response; } /** * Gets the Drupal time service. * * @return \Drupal\Component\Datetime\TimeInterface * The interface for getting access to request time. */ protected function getTimeService(): TimeInterface { return $this->timeService; } /** * {@inheritdoc} */ public function getRouteResourceTypes(Route $route, string $route_name): array { $resource_type = new ResourceType( 'bug_repro', 'bug_repro', NULL, FALSE, TRUE, FALSE, FALSE, [] ); return [$resource_type]; } } - Ensure that routing in the custom module is setup for the resource:
bug_repro: path: '/%jsonapi%/bug_repro' defaults: _jsonapi_resource: '\Drupal\example\Resource\BugReproResource' requirements: _access: 'TRUE' - Clear site caches.
- In a private browsing window, request
/jsonapi/bug_repro. - Over a span of about five seconds, keep refreshing the page, paying attention to the
timestampattribute returned in the resource. - Now, modify the routing to add the
no_cacheoption, like so: - Ensure that routing in the custom module is setup for the resource:
bug_repro: path: '/%jsonapi%/bug_repro' defaults: _jsonapi_resource: '\Drupal\example\Resource\BugReproResource' requirements: _access: 'TRUE' options: no_cache: TRUE - Clear site caches.
- In a private browsing window, request
/jsonapi/bug_repro. - Over a span of about five seconds, keep refreshing the page, paying attention to the
timestampattribute returned in the resource.
You will notice that the timestamp does not increment unless the route is set not to cache, even though the cacheability of the response is set to not cache.
Proposed resolution
If any element of a page indicates a cache max age of 0, the page should not be cacheable.
Remaining tasks
Either:
- Add a response cache policy that respects content cacheability;
OR - Correct cacheability logic in
\Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()to properly detect bubbling of cacheability from page content.
Comments
Comment #6
larowlanIs this a duplicate of #2352009: Bubbling of elements' max-age to the page's headers and the page cache?
Comment #7
acbramley commentedI'm closing this as it does seem likely a duplicate as per #6.
Please reopen if this is incorrect.