Skip to content

Adding response mutator. #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jan 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## 1.6.0 - 2017-01-29

### Added

* Added `cache_listeners` option, which takes an array of `CacheListener`s, who get notified and can optionally act on a Response based on a cache hit or miss event. An implementation, `AddHeaderCacheListener`, is provided which will add an `X-Cache` header to the response with this information.

## 1.5.0 - 2017-11-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
"dev-master": "1.6-dev"
}
}
}
42 changes: 42 additions & 0 deletions src/Cache/Listener/AddHeaderCacheListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Http\Client\Common\Plugin\Cache\Listener;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Cache\CacheItemInterface;

/**
* Adds a header indicating if the response came from cache.
*
* @author Iain Connor <[email protected]>
*/
class AddHeaderCacheListener implements CacheListener
{
/** @var string */
private $headerName;

/**
* @param string $headerName
*/
public function __construct($headerName = 'X-Cache')
{
$this->headerName = $headerName;
}

/**
* Called before the cache plugin returns the response, with information on whether that response came from cache.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param bool $fromCache Whether the `$response` was from the cache or not.
* Note that checking `$cacheItem->isHit()` is not sufficent to determine this.
* @param CacheItemInterface|null $cacheItem
*
* @return ResponseInterface
*/
public function onCacheResponse(RequestInterface $request, ResponseInterface $response, $fromCache, $cacheItem)
{
return $response->withHeader($this->headerName, $fromCache ? 'HIT' : 'MISS');
}
}
30 changes: 30 additions & 0 deletions src/Cache/Listener/CacheListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Http\Client\Common\Plugin\Cache\Listener;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Cache\CacheItemInterface;

/**
* Called by the cache plugin with information on the cache status.
* Provides an opportunity to update the response based on whether the cache was a hit or a miss, or
* other cache-meta-data.
*
* @author Iain Connor <[email protected]>
*/
interface CacheListener
{
/**
* Called before the cache plugin returns the response, with information on whether that response came from cache.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param bool $fromCache Whether the `$response` was from the cache or not.
* Note that checking `$cacheItem->isHit()` is not sufficent to determine this.
* @param CacheItemInterface|null $cacheItem
*
* @return ResponseInterface
*/
public function onCacheResponse(RequestInterface $request, ResponseInterface $response, $fromCache, $cacheItem);
}
43 changes: 37 additions & 6 deletions src/CachePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Http\Client\Common\Plugin\Exception\RewindStreamException;
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator;
use Http\Client\Common\Plugin\Cache\Generator\SimpleGenerator;
use Http\Client\Common\Plugin\Cache\Listener\CacheListener;
use Http\Message\StreamFactory;
use Http\Promise\FulfilledPromise;
use Psr\Cache\CacheItemInterface;
Expand Down Expand Up @@ -59,6 +60,8 @@ final class CachePlugin implements Plugin
* @var array $methods list of request methods which can be cached
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses
* @var CacheKeyGenerator $cache_key_generator an object to generate the cache key. Defaults to a new instance of SimpleGenerator
* @var CacheListener[] $cache_listeners an array of objects to act on the response based on the results of the cache check.
* Defaults to an empty array
* }
*/
public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
Expand Down Expand Up @@ -129,7 +132,11 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
$method = strtoupper($request->getMethod());
// if the request not is cachable, move to $next
if (!in_array($method, $this->config['methods'])) {
return $next($request);
return $next($request)->then(function (ResponseInterface $response) use ($request) {
$response = $this->handleCacheListeners($request, $response, false, null);

return $response;
});
}

// If we can cache the request
Expand All @@ -141,7 +148,10 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
// The array_key_exists() is to be removed in 2.0.
if (array_key_exists('expiresAt', $data) && (null === $data['expiresAt'] || time() < $data['expiresAt'])) {
// This item is still valid according to previous cache headers
return new FulfilledPromise($this->createResponseFromCacheItem($cacheItem));
$response = $this->createResponseFromCacheItem($cacheItem);
$response = $this->handleCacheListeners($request, $response, true, $cacheItem);

return new FulfilledPromise($response);
}

// Add headers to ask the server if this cache is still valid
Expand All @@ -154,14 +164,14 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
}
}

return $next($request)->then(function (ResponseInterface $response) use ($cacheItem) {
return $next($request)->then(function (ResponseInterface $response) use ($request, $cacheItem) {
if (304 === $response->getStatusCode()) {
if (!$cacheItem->isHit()) {
/*
* We do not have the item in cache. This plugin did not add If-Modified-Since
* or If-None-Match headers. Return the response from server.
*/
return $response;
return $this->handleCacheListeners($request, $response, false, $cacheItem);
}

// The cached response we have is still valid
Expand All @@ -171,7 +181,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
$cacheItem->set($data)->expiresAfter($this->calculateCacheItemExpiresAfter($maxAge));
$this->pool->save($cacheItem);

return $this->createResponseFromCacheItem($cacheItem);
return $this->handleCacheListeners($request, $this->createResponseFromCacheItem($cacheItem), true, $cacheItem);
}

if ($this->isCacheable($response)) {
Expand All @@ -196,7 +206,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
$this->pool->save($cacheItem);
}

return $response;
return $this->handleCacheListeners($request, $response, false, isset($cacheItem) ? $cacheItem : null);
});
}

Expand Down Expand Up @@ -343,6 +353,7 @@ private function configureOptions(OptionsResolver $resolver)
'methods' => ['GET', 'HEAD'],
'respect_response_cache_directives' => ['no-cache', 'private', 'max-age', 'no-store'],
'cache_key_generator' => null,
'cache_listeners' => [],
]);

$resolver->setAllowedTypes('cache_lifetime', ['int', 'null']);
Expand All @@ -357,6 +368,7 @@ private function configureOptions(OptionsResolver $resolver)

return empty($matches);
});
$resolver->setAllowedTypes('cache_listeners', ['array']);

$resolver->setNormalizer('respect_cache_headers', function (Options $options, $value) {
if (null !== $value) {
Expand Down Expand Up @@ -441,4 +453,23 @@ private function getETag(CacheItemInterface $cacheItem)
}
}
}

/**
* Call the cache listeners, if they are set.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param bool $cacheHit
* @param CacheItemInterface|null $cacheItem
*
* @return ResponseInterface
*/
private function handleCacheListeners(RequestInterface $request, ResponseInterface $response, $cacheHit, $cacheItem)
{
foreach ($this->config['cache_listeners'] as $cacheListener) {
$response = $cacheListener->onCacheResponse($request, $response, $cacheHit, $cacheItem);
}

return $response;
}
}