Skip to content

Commit

Permalink
feat(cache): Add ETag support into the cache engine (#34)
Browse files Browse the repository at this point in the history
Upgrade Eseye design to handle ETag in its cache engine.

Closes #33
  • Loading branch information
warlof authored Mar 30, 2019
1 parent 076dc3d commit 05653e2
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 19 deletions.
4 changes: 2 additions & 2 deletions src/Cache/FileCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ public function get(string $uri, string $query = '')
// Get the data from the file and unserialize it
$file = unserialize(file_get_contents($cache_file_path));

// If the cached entry is expired, remove it.
if ($file->expired()) {
// If the cached entry is expired and does not have any ETag, remove it.
if ($file->expired() && ! $file->hasHeader('ETag')) {

$this->forget($uri, $query);

Expand Down
3 changes: 2 additions & 1 deletion src/Cache/MemcachedCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ public function get(string $uri, string $query = '')

$data = unserialize($value);

if ($data->expired()) {
// If the cached entry is expired and does not have any ETag, remove it.
if ($data->expired() && ! $data->hasHeader('ETag')) {
$this->forget($uri, $query);

return false;
Expand Down
3 changes: 2 additions & 1 deletion src/Cache/RedisCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ public function get(string $uri, string $query = '')
$data = unserialize($this->redis
->get($this->buildCacheKey($uri, $query)));

if ($data->expired()) {
// If the cached entry is expired and does not have any ETag, remove it.
if ($data->expired() && ! $data->hasHeader('ETag')) {

$this->forget($uri, $query);

Expand Down
42 changes: 37 additions & 5 deletions src/Containers/EsiResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function __construct(
$this->expires_at = strlen($expires) > 2 ? $expires : 'now';
$this->response_code = $response_code;

// If there is an error, set that
// If there is an error, set that.
if (property_exists($data, 'error'))
$this->error_message = $data->error;

Expand Down Expand Up @@ -152,10 +152,10 @@ private function parseHeaders(array $headers)
// Check for some header values that might be interesting
// such as the current error limit and number of pages
// available.
array_key_exists('X-Esi-Error-Limit-Remain', $headers) ?
$this->error_limit = (int) $headers['X-Esi-Error-Limit-Remain'] : null;
$this->hasHeader('X-Esi-Error-Limit-Remain') ?
$this->error_limit = (int) $this->getHeader('X-Esi-Error-Limit-Remain') : null;

array_key_exists('X-Pages', $headers) ? $this->pages = (int) $headers['X-Pages'] : null;
$this->hasHeader('X-Pages') ? $this->pages = (int) $this->getHeader('X-Pages') : null;
}

/**
Expand Down Expand Up @@ -228,7 +228,7 @@ public function getErrorCode(): int
/**
* @return bool
*/
public function setIsCachedload(): bool
public function setIsCachedLoad(): bool
{

return $this->cached_load = true;
Expand All @@ -242,4 +242,36 @@ public function isCachedLoad(): bool

return $this->cached_load;
}

/**
* @param string $name
* @return bool
*/
public function hasHeader(string $name)
{
// turn headers into case insensitive array
$key_map = array_change_key_case($this->headers, CASE_LOWER);

// track for the requested header name
return array_key_exists(strtolower($name), $key_map);
}

/**
* @param string $name
* @return mixed|null
*/
public function getHeader(string $name)
{
// turn header name into case insensitive
$insensitive_key = strtolower($name);

// turn headers into case insensitive array
$key_map = array_change_key_case($this->headers, CASE_LOWER);

// track for the requested header name and return its value if exists
if (array_key_exists($insensitive_key, $key_map))
return $key_map[$insensitive_key];

return null;
}
}
47 changes: 37 additions & 10 deletions src/Eseye.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,10 @@ public function setBody(array $body): self
*
* @return \Seat\Eseye\Containers\EsiResponse
* @throws \Seat\Eseye\Exceptions\EsiScopeAccessDeniedException
* @throws \Seat\Eseye\Exceptions\UriDataMissingException
* @throws \Seat\Eseye\Exceptions\RequestFailedException
* @throws \Seat\Eseye\Exceptions\InvalidAuthenticationException
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
* @throws \Seat\Eseye\Exceptions\UriDataMissingException
*/
public function invoke(string $method, string $uri, array $uri_data = []): EsiResponse
{
Expand Down Expand Up @@ -235,17 +237,39 @@ public function invoke(string $method, string $uri, array $uri_data = []): EsiRe
$cached = $this->getCache()->get($uri->getPath(), $uri->getQuery())
) {

// Mark the response as one that was loaded from the cache
$cached->setIsCachedload();
// Mark the response as one that was loaded from the cache in case no ETag exists
if (! $cached->hasHeader('ETag'))
$cached->setIsCachedLoad();

// Handling ETag marked response specifically (ignoring the expired time)
// Sending a request with the stored ETag in header - if we have a 304 response, data has not been altered.
if ($cached->hasHeader('ETag') && $cached->expired()) {

$result = $this->rawFetch($method, $uri, $this->getBody(), ['If-None-Match' => $cached->getHeader('ETag')]);

if ($result->getErrorCode() == 304)
$cached->setIsCachedLoad();
}

// In case the result is effectively retrieved from cache,
// return the cached element.
if ($cached->isCachedLoad()) {

// Perform some debug logging
$logging_msg = 'Loaded cached response for ' . $method . ' -> ' . $uri;

// Perform some debug logging
$this->getLogger()->debug('Loaded cached response for ' . $method . ' -> ' . $uri);
if ($cached->hasHeader('ETag'))
$logging_msg = sprintf('%s [%s]', $logging_msg, $cached->getHeader('ETag'));

return $cached;
$this->getLogger()->debug($logging_msg);

return $cached;
}
}

// Call ESI itself and get the EsiResponse
$result = $this->rawFetch($method, $uri, $this->getBody());
// Call ESI itself and get the EsiResponse in case it has not already been handled with cache control
if (! isset($result))
$result = $this->rawFetch($method, $uri, $this->getBody());

// Cache the response if it was a get and is not already expired
if (in_array(strtolower($method), $this->cachable_verb) && ! $result->expired())
Expand Down Expand Up @@ -431,14 +455,17 @@ private function getCache(): CacheInterface
* @param string $method
* @param string $uri
* @param array $body
* @param array $headers
*
* @return mixed
* @throws \Seat\Eseye\Exceptions\InvalidAuthenticationException
* @throws \Seat\Eseye\Exceptions\RequestFailedException
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
*/
public function rawFetch(string $method, string $uri, array $body)
public function rawFetch(string $method, string $uri, array $body, array $headers = [])
{

return $this->getFetcher()->call($method, $uri, $body);
return $this->getFetcher()->call($method, $uri, $body, $headers);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/Fetchers/FetcherInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ interface FetcherInterface
* @param array $headers
*
* @return \Seat\Eseye\Containers\EsiResponse
* @throws \Seat\Eseye\Exceptions\InvalidAuthenticationException
* @throws \Seat\Eseye\Exceptions\RequestFailedException
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
*/
public function call(string $method, string $uri, array $body, array $headers = []): EsiResponse;

Expand Down

0 comments on commit 05653e2

Please sign in to comment.