Skip to content
Snippets Groups Projects
Commit f45b367c authored by Lio Novelli's avatar Lio Novelli
Browse files

Issue #3450841 by useernamee, petar_basic: Caching issue

parent bed75002
No related branches found
No related tags found
1 merge request!23Resolve #3450841 "Caching issue"
......@@ -82,3 +82,21 @@ about cron runs. Currently only default drupal cron is supported.
The cron ping uri can be overridden with `OHDEAR_CRON_URI` environment
variable.
### Caching
The healthcheck results endpoint allows caching but it is disabled by default.
To enable the caching manually add `healthcheck_cache_max_age` to the
`ohdear_integration.settings.yml` config file with the value in seconds how
long the resource should be cached.
If `healthcheck_cache_max_age` configuration option is not provided, the
resource is not cached.
For this functionality to work properly requests to `/json/oh-dear-health-check-results`
endpoint should come with the secret provided as URL query parameter and not
as secret header - if you have Drupal's internal page cache installed. Internal
page cache does not respect Cache contexts. One tricky way to resolve this issue
is to add a redirect for requests to endpoint with the `oh-dear-health-check-secret`
header to the same enpoint but with the URL query parameter
`?oh-dear-health-check-secret=<value-of-header>`.
......@@ -4,7 +4,6 @@ namespace Drupal\ohdear_integration\Controller;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableJsonResponse;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
......@@ -22,6 +21,7 @@ use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
* Returns responses for OhDear Integration routes.
*/
class OhDearIntegrationController extends ControllerBase {
use LoggerChannelTrait;
/**
......@@ -59,6 +59,13 @@ class OhDearIntegrationController extends ControllerBase {
*/
protected $cacheMaxAge;
/**
* Timestamp when monitoring results were computed.
*
* @var int|string|null
*/
protected $finishedAt;
/**
* Constructs OhDear integration controller.
*
......@@ -112,30 +119,25 @@ class OhDearIntegrationController extends ControllerBase {
403,
[
'Cache-Control' => 'private, no-cache, no-store, must-revalidate',
'Last-Modified' => $this->formatDateString($this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') ?? time()),
],
TRUE
);
}
else {
$data = $this->ohDearGenerator->getData();
$max_age = $this->getCacheMaxAge();
$this->finishedAt = json_decode($data, TRUE)['finishedAt'] ?? '0';
$json_response = new CacheableJsonResponse(
$data,
200,
[
'Cache-Control' => "public,max-age=$max_age,s-maxage=$max_age",
],
$this->computeHeaders(),
TRUE
);
}
// Cache is mostly set outside of context.
$this->addCacheToResponse($json_response);
$cacheable_metadata = $json_response->getCacheableMetadata();
$this->refineCacheMetadata($cacheable_metadata);
$json_response->addCacheableDependency($cacheable_metadata);
return $json_response;
});
$this->addCacheToResponse($response);
// If there is metadata left on the context, apply it on the response.
if (!$context->isEmpty()) {
if ($metadata = $context->pop()) {
......@@ -146,23 +148,50 @@ class OhDearIntegrationController extends ControllerBase {
return $response;
}
/**
* Return the header array.
*
* @return array
* Array of header names as keys and values as values.
*
* @throws \Exception
*/
protected function computeHeaders(): array {
$max_age = $this->getCacheMaxAge() ?? 0;
$headers = [];
if ($max_age) {
$finishedAt = $this->finishedAt ?? 0;
if (!$finishedAt) {
$this->getLogger('ohdear_integration')->warning('FinishedAt time is missing.');
}
$expires = (int) $finishedAt + (int) $max_age;
$headers['Cache-Control'] = "public,max-age=$max_age,s-maxage=$max_age";
$headers['Last-Modified'] = $this->formatDateString($finishedAt);
$headers['Expires'] = $this->formatDateString($expires);
$headers['ETag'] = $finishedAt;
}
else {
$headers['Cache-Control'] = 'private,no-cache,must-revalidate';
$headers['Last-Modified'] = $this->formatDateString($this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') ?? time());
}
return $headers;
}
/**
* Add cache configuration to response.
*
* @param CacheableResponseInterface $response
* @param \Drupal\Core\Cache\CacheableResponseInterface $response
* Response to add cache to.
*
* @return CacheableResponseInterface
* @return \Drupal\Core\Cache\CacheableResponseInterface
* Response with cache added.
*/
protected function addCacheToResponse(CacheableResponseInterface $response) {
$cache_max_age = $this->getCacheMaxAge();
if (!$cache_max_age || !$response->isCacheable()) {
// 403 response is not cacheable.
$response->headers->set('Cache-Control', 'private,no-cache,must-revalidate');
}
else {
$response->headers->set('Cache-Control', "public,max-age=$cache_max_age,s-maxage=$cache_max_age");
if (!$response->headers->has('Cache-Control')) {
$headers = $this->computeHeaders();
foreach ($headers as $header => $value) {
$response->headers->set($header, $value);
}
}
$cacheable_metadata = $response->getCacheableMetadata();
$this->refineCacheMetadata($cacheable_metadata);
......@@ -171,7 +200,23 @@ class OhDearIntegrationController extends ControllerBase {
}
/**
* Get cache max age configuration
* Adds cache metadata related to the healthheck.
*
* @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheable_dependency
* An object implementing RefinableCacheableDependencyInterface.
*/
protected function refineCacheMetadata(RefinableCacheableDependencyInterface $cacheable_dependency) {
$cacheable_dependency->addCacheContexts([
'url.query_args:oh-dear-health-check-secret',
'headers:oh-dear-health-check-secret',
'user.roles',
]);
$cacheable_dependency->addCacheTags(['monitoring_sensor_result']);
$cacheable_dependency->setCacheMaxAge($this->getCacheMaxAge() ?? 0);
}
/**
* Get cache max age configuration.
*
* @return mixed
*/
......@@ -205,19 +250,20 @@ class OhDearIntegrationController extends ControllerBase {
}
/**
* Adds cache metadata related to the healthheck.
* Convert timestamp to date string.
*
* @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheable_dependency
* An object implementing RefinableCacheableDependencyInterface.
* @param string $timestamp
* Timestamp.
*
* @return string
* Formatted date time.
*
* @throws \Exception
*/
protected function refineCacheMetadata(RefinableCacheableDependencyInterface $cacheable_dependency) {
$cacheable_dependency->addCacheContexts([
'url.query_args:oh-dear-health-check-secret',
'headers:oh-dear-health-check-secret',
'user.roles',
]);
$cacheable_dependency->addCacheTags(['monitoring_sensor_result']);
$cacheable_dependency->setCacheMaxAge($this->getCacheMaxAge() ?? 0);
protected function formatDateString(string $timestamp): string {
$date = new \DateTimeImmutable('@' . $timestamp);
$date = $date->setTimezone(new \DateTimeZone('UTC'));
return $date->format('D, d M Y H:i:s') . ' GMT';
}
}
......@@ -57,6 +57,13 @@ class OhDearSdkService {
*/
protected $ohDearCronUri;
/**
* Value to be used in max-age property of Cache-Control header.
*
* @var string|int|null
*/
protected $ohDearHealthcheckCacheMaxAge;
/**
* Constructs an OhDearSdkService object.
*
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment