Initial import.

This commit is contained in:
ekes 2015-02-24 16:25:12 +01:00
commit 86383280c9
428 changed files with 68738 additions and 0 deletions

View file

@ -0,0 +1,11 @@
<?php
namespace Guzzle\Plugin\Cache;
\Guzzle\Common\Version::warn('Guzzle\Plugin\Cache\CacheKeyProviderInterface is no longer used');
/**
* @deprecated This is no longer used
* @codeCoverageIgnore
*/
interface CacheKeyProviderInterface {}

View file

@ -0,0 +1,353 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Cache\CacheAdapterFactory;
use Guzzle\Cache\CacheAdapterInterface;
use Guzzle\Common\Event;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Version;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Cache\DoctrineCacheAdapter;
use Guzzle\Http\Exception\CurlException;
use Doctrine\Common\Cache\ArrayCache;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Plugin to enable the caching of GET and HEAD requests. Caching can be done on all requests passing through this
* plugin or only after retrieving resources with cacheable response headers.
*
* This is a simple implementation of RFC 2616 and should be considered a private transparent proxy cache, meaning
* authorization and private data can be cached.
*
* It also implements RFC 5861's `stale-if-error` Cache-Control extension, allowing stale cache responses to be used
* when an error is encountered (such as a `500 Internal Server Error` or DNS failure).
*/
class CachePlugin implements EventSubscriberInterface
{
/** @var RevalidationInterface Cache revalidation strategy */
protected $revalidation;
/** @var CanCacheStrategyInterface Object used to determine if a request can be cached */
protected $canCache;
/** @var CacheStorageInterface $cache Object used to cache responses */
protected $storage;
/** @var bool */
protected $autoPurge;
/**
* @param array|CacheAdapterInterface|CacheStorageInterface $options Array of options for the cache plugin,
* cache adapter, or cache storage object.
* - CacheStorageInterface storage: Adapter used to cache responses
* - RevalidationInterface revalidation: Cache revalidation strategy
* - CanCacheInterface can_cache: Object used to determine if a request can be cached
* - bool auto_purge Set to true to automatically PURGE resources when non-idempotent
* requests are sent to a resource. Defaults to false.
* @throws InvalidArgumentException if no cache is provided and Doctrine cache is not installed
*/
public function __construct($options = null)
{
if (!is_array($options)) {
if ($options instanceof CacheAdapterInterface) {
$options = array('storage' => new DefaultCacheStorage($options));
} elseif ($options instanceof CacheStorageInterface) {
$options = array('storage' => $options);
} elseif ($options) {
$options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options)));
} elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) {
// @codeCoverageIgnoreStart
throw new InvalidArgumentException('No cache was provided and Doctrine is not installed');
// @codeCoverageIgnoreEnd
}
}
$this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false;
// Add a cache storage if a cache adapter was provided
$this->storage = isset($options['storage'])
? $options['storage']
: new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()));
if (!isset($options['can_cache'])) {
$this->canCache = new DefaultCanCacheStrategy();
} else {
$this->canCache = is_callable($options['can_cache'])
? new CallbackCanCacheStrategy($options['can_cache'])
: $options['can_cache'];
}
// Use the provided revalidation strategy or the default
$this->revalidation = isset($options['revalidation'])
? $options['revalidation']
: new DefaultRevalidation($this->storage, $this->canCache);
}
public static function getSubscribedEvents()
{
return array(
'request.before_send' => array('onRequestBeforeSend', -255),
'request.sent' => array('onRequestSent', 255),
'request.error' => array('onRequestError', 0),
'request.exception' => array('onRequestException', 0),
);
}
/**
* Check if a response in cache will satisfy the request before sending
*
* @param Event $event
*/
public function onRequestBeforeSend(Event $event)
{
$request = $event['request'];
$request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
if (!$this->canCache->canCacheRequest($request)) {
switch ($request->getMethod()) {
case 'PURGE':
$this->purge($request);
$request->setResponse(new Response(200, array(), 'purged'));
break;
case 'PUT':
case 'POST':
case 'DELETE':
case 'PATCH':
if ($this->autoPurge) {
$this->purge($request);
}
}
return;
}
if ($response = $this->storage->fetch($request)) {
$params = $request->getParams();
$params['cache.lookup'] = true;
$response->setHeader(
'Age',
time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now')
);
// Validate that the response satisfies the request
if ($this->canResponseSatisfyRequest($request, $response)) {
if (!isset($params['cache.hit'])) {
$params['cache.hit'] = true;
}
$request->setResponse($response);
}
}
}
/**
* If possible, store a response in cache after sending
*
* @param Event $event
*/
public function onRequestSent(Event $event)
{
$request = $event['request'];
$response = $event['response'];
if ($request->getParams()->get('cache.hit') === null &&
$this->canCache->canCacheRequest($request) &&
$this->canCache->canCacheResponse($response)
) {
$this->storage->cache($request, $response);
}
$this->addResponseHeaders($request, $response);
}
/**
* If possible, return a cache response on an error
*
* @param Event $event
*/
public function onRequestError(Event $event)
{
$request = $event['request'];
if (!$this->canCache->canCacheRequest($request)) {
return;
}
if ($response = $this->storage->fetch($request)) {
$response->setHeader(
'Age',
time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now')
);
if ($this->canResponseSatisfyFailedRequest($request, $response)) {
$request->getParams()->set('cache.hit', 'error');
$this->addResponseHeaders($request, $response);
$event['response'] = $response;
$event->stopPropagation();
}
}
}
/**
* If possible, set a cache response on a cURL exception
*
* @param Event $event
*
* @return null
*/
public function onRequestException(Event $event)
{
if (!$event['exception'] instanceof CurlException) {
return;
}
$request = $event['request'];
if (!$this->canCache->canCacheRequest($request)) {
return;
}
if ($response = $this->storage->fetch($request)) {
$response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now'));
if (!$this->canResponseSatisfyFailedRequest($request, $response)) {
return;
}
$request->getParams()->set('cache.hit', 'error');
$request->setResponse($response);
$this->addResponseHeaders($request, $response);
$event->stopPropagation();
}
}
/**
* Check if a cache response satisfies a request's caching constraints
*
* @param RequestInterface $request Request to validate
* @param Response $response Response to validate
*
* @return bool
*/
public function canResponseSatisfyRequest(RequestInterface $request, Response $response)
{
$responseAge = $response->calculateAge();
$reqc = $request->getHeader('Cache-Control');
$resc = $response->getHeader('Cache-Control');
// Check the request's max-age header against the age of the response
if ($reqc && $reqc->hasDirective('max-age') &&
$responseAge > $reqc->getDirective('max-age')) {
return false;
}
// Check the response's max-age header
if ($response->isFresh() === false) {
$maxStale = $reqc ? $reqc->getDirective('max-stale') : null;
if (null !== $maxStale) {
if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) {
return false;
}
} elseif ($resc && $resc->hasDirective('max-age')
&& $responseAge > $resc->getDirective('max-age')
) {
return false;
}
}
if ($this->revalidation->shouldRevalidate($request, $response)) {
try {
return $this->revalidation->revalidate($request, $response);
} catch (CurlException $e) {
$request->getParams()->set('cache.hit', 'error');
return $this->canResponseSatisfyFailedRequest($request, $response);
}
}
return true;
}
/**
* Check if a cache response satisfies a failed request's caching constraints
*
* @param RequestInterface $request Request to validate
* @param Response $response Response to validate
*
* @return bool
*/
public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response)
{
$reqc = $request->getHeader('Cache-Control');
$resc = $response->getHeader('Cache-Control');
$requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null;
$responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null;
if (!$requestStaleIfError && !$responseStaleIfError) {
return false;
}
if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) {
return false;
}
if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) {
return false;
}
return true;
}
/**
* Purge all cache entries for a given URL
*
* @param string $url URL to purge
*/
public function purge($url)
{
// BC compatibility with previous version that accepted a Request object
$url = $url instanceof RequestInterface ? $url->getUrl() : $url;
$this->storage->purge($url);
}
/**
* Add the plugin's headers to a response
*
* @param RequestInterface $request Request
* @param Response $response Response to add headers to
*/
protected function addResponseHeaders(RequestInterface $request, Response $response)
{
$params = $request->getParams();
$response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
$lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache';
if ($header = $response->getHeader('X-Cache-Lookup')) {
// Don't add duplicates
$values = $header->toArray();
$values[] = $lookup;
$response->setHeader('X-Cache-Lookup', array_unique($values));
} else {
$response->setHeader('X-Cache-Lookup', $lookup);
}
if ($params['cache.hit'] === true) {
$xcache = 'HIT from GuzzleCache';
} elseif ($params['cache.hit'] == 'error') {
$xcache = 'HIT_ERROR from GuzzleCache';
} else {
$xcache = 'MISS from GuzzleCache';
}
if ($header = $response->getHeader('X-Cache')) {
// Don't add duplicates
$values = $header->toArray();
$values[] = $xcache;
$response->setHeader('X-Cache', array_unique($values));
} else {
$response->setHeader('X-Cache', $xcache);
}
if ($response->isFresh() === false) {
$response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION));
if ($params['cache.hit'] === 'error') {
$response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION));
}
}
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Interface used to cache HTTP requests
*/
interface CacheStorageInterface
{
/**
* Get a Response from the cache for a request
*
* @param RequestInterface $request
*
* @return null|Response
*/
public function fetch(RequestInterface $request);
/**
* Cache an HTTP request
*
* @param RequestInterface $request Request being cached
* @param Response $response Response to cache
*/
public function cache(RequestInterface $request, Response $response);
/**
* Deletes cache entries that match a request
*
* @param RequestInterface $request Request to delete from cache
*/
public function delete(RequestInterface $request);
/**
* Purge all cache entries for a given URL
*
* @param string $url
*/
public function purge($url);
}

View file

@ -0,0 +1,53 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Determines if a request can be cached using a callback
*/
class CallbackCanCacheStrategy extends DefaultCanCacheStrategy
{
/** @var callable Callback for request */
protected $requestCallback;
/** @var callable Callback for response */
protected $responseCallback;
/**
* @param \Closure|array|mixed $requestCallback Callable method to invoke for requests
* @param \Closure|array|mixed $responseCallback Callable method to invoke for responses
*
* @throws InvalidArgumentException
*/
public function __construct($requestCallback = null, $responseCallback = null)
{
if ($requestCallback && !is_callable($requestCallback)) {
throw new InvalidArgumentException('Method must be callable');
}
if ($responseCallback && !is_callable($responseCallback)) {
throw new InvalidArgumentException('Method must be callable');
}
$this->requestCallback = $requestCallback;
$this->responseCallback = $responseCallback;
}
public function canCacheRequest(RequestInterface $request)
{
return $this->requestCallback
? call_user_func($this->requestCallback, $request)
: parent::canCacheRequest($request);
}
public function canCacheResponse(Response $response)
{
return $this->responseCallback
? call_user_func($this->responseCallback, $response)
: parent::canCacheResponse($response);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Strategy used to determine if a request can be cached
*/
interface CanCacheStrategyInterface
{
/**
* Determine if a request can be cached
*
* @param RequestInterface $request Request to determine
*
* @return bool
*/
public function canCacheRequest(RequestInterface $request);
/**
* Determine if a response can be cached
*
* @param Response $response Response to determine
*
* @return bool
*/
public function canCacheResponse(Response $response);
}

View file

@ -0,0 +1,46 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
\Guzzle\Common\Version::warn('Guzzle\Plugin\Cache\DefaultCacheKeyProvider is no longer used');
/**
* @deprecated This class is no longer used
* @codeCoverageIgnore
*/
class DefaultCacheKeyProvider implements CacheKeyProviderInterface
{
public function getCacheKey(RequestInterface $request)
{
// See if the key has already been calculated
$key = $request->getParams()->get(self::CACHE_KEY);
if (!$key) {
$cloned = clone $request;
$cloned->removeHeader('Cache-Control');
// Check to see how and if the key should be filtered
foreach (explode(';', $request->getParams()->get(self::CACHE_KEY_FILTER)) as $part) {
$pieces = array_map('trim', explode('=', $part));
if (isset($pieces[1])) {
foreach (array_map('trim', explode(',', $pieces[1])) as $remove) {
if ($pieces[0] == 'header') {
$cloned->removeHeader($remove);
} elseif ($pieces[0] == 'query') {
$cloned->getQuery()->remove($remove);
}
}
}
}
$raw = (string) $cloned;
$key = 'GZ' . md5($raw);
$request->getParams()->set(self::CACHE_KEY, $key)->set(self::CACHE_KEY_RAW, $raw);
}
return $key;
}
}

View file

@ -0,0 +1,251 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Cache\CacheAdapterFactory;
use Guzzle\Cache\CacheAdapterInterface;
use Guzzle\Http\EntityBodyInterface;
use Guzzle\Http\Message\MessageInterface;
use Guzzle\Http\Message\Request;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Default cache storage implementation
*/
class DefaultCacheStorage implements CacheStorageInterface
{
/** @var string */
protected $keyPrefix;
/** @var CacheAdapterInterface Cache used to store cache data */
protected $cache;
/** @var int Default cache TTL */
protected $defaultTtl;
/**
* @param mixed $cache Cache used to store cache data
* @param string $keyPrefix Provide an optional key prefix to prefix on all cache keys
* @param int $defaultTtl Default cache TTL
*/
public function __construct($cache, $keyPrefix = '', $defaultTtl = 3600)
{
$this->cache = CacheAdapterFactory::fromCache($cache);
$this->defaultTtl = $defaultTtl;
$this->keyPrefix = $keyPrefix;
}
public function cache(RequestInterface $request, Response $response)
{
$currentTime = time();
$ttl = $request->getParams()->get('cache.override_ttl') ?: $response->getMaxAge() ?: $this->defaultTtl;
if ($cacheControl = $response->getHeader('Cache-Control')) {
$stale = $cacheControl->getDirective('stale-if-error');
$ttl += $stale == true ? $ttl : $stale;
}
// Determine which manifest key should be used
$key = $this->getCacheKey($request);
$persistedRequest = $this->persistHeaders($request);
$entries = array();
if ($manifest = $this->cache->fetch($key)) {
// Determine which cache entries should still be in the cache
$vary = $response->getVary();
foreach (unserialize($manifest) as $entry) {
// Check if the entry is expired
if ($entry[4] < $currentTime) {
continue;
}
$entry[1]['vary'] = isset($entry[1]['vary']) ? $entry[1]['vary'] : '';
if ($vary != $entry[1]['vary'] || !$this->requestsMatch($vary, $entry[0], $persistedRequest)) {
$entries[] = $entry;
}
}
}
// Persist the response body if needed
$bodyDigest = null;
if ($response->getBody() && $response->getBody()->getContentLength() > 0) {
$bodyDigest = $this->getBodyKey($request->getUrl(), $response->getBody());
$this->cache->save($bodyDigest, (string) $response->getBody(), $ttl);
}
array_unshift($entries, array(
$persistedRequest,
$this->persistHeaders($response),
$response->getStatusCode(),
$bodyDigest,
$currentTime + $ttl
));
$this->cache->save($key, serialize($entries));
}
public function delete(RequestInterface $request)
{
$key = $this->getCacheKey($request);
if ($entries = $this->cache->fetch($key)) {
// Delete each cached body
foreach (unserialize($entries) as $entry) {
if ($entry[3]) {
$this->cache->delete($entry[3]);
}
}
$this->cache->delete($key);
}
}
public function purge($url)
{
foreach (array('GET', 'HEAD', 'POST', 'PUT', 'DELETE') as $method) {
$this->delete(new Request($method, $url));
}
}
public function fetch(RequestInterface $request)
{
$key = $this->getCacheKey($request);
if (!($entries = $this->cache->fetch($key))) {
return null;
}
$match = null;
$headers = $this->persistHeaders($request);
$entries = unserialize($entries);
foreach ($entries as $index => $entry) {
if ($this->requestsMatch(isset($entry[1]['vary']) ? $entry[1]['vary'] : '', $headers, $entry[0])) {
$match = $entry;
break;
}
}
if (!$match) {
return null;
}
// Ensure that the response is not expired
$response = null;
if ($match[4] < time()) {
$response = -1;
} else {
$response = new Response($match[2], $match[1]);
if ($match[3]) {
if ($body = $this->cache->fetch($match[3])) {
$response->setBody($body);
} else {
// The response is not valid because the body was somehow deleted
$response = -1;
}
}
}
if ($response === -1) {
// Remove the entry from the metadata and update the cache
unset($entries[$index]);
if ($entries) {
$this->cache->save($key, serialize($entries));
} else {
$this->cache->delete($key);
}
return null;
}
return $response;
}
/**
* Hash a request URL into a string that returns cache metadata
*
* @param RequestInterface $request
*
* @return string
*/
protected function getCacheKey(RequestInterface $request)
{
// Allow cache.key_filter to trim down the URL cache key by removing generate query string values (e.g. auth)
if ($filter = $request->getParams()->get('cache.key_filter')) {
$url = $request->getUrl(true);
foreach (explode(',', $filter) as $remove) {
$url->getQuery()->remove(trim($remove));
}
} else {
$url = $request->getUrl();
}
return $this->keyPrefix . md5($request->getMethod() . ' ' . $url);
}
/**
* Create a cache key for a response's body
*
* @param string $url URL of the entry
* @param EntityBodyInterface $body Response body
*
* @return string
*/
protected function getBodyKey($url, EntityBodyInterface $body)
{
return $this->keyPrefix . md5($url) . $body->getContentMd5();
}
/**
* Determines whether two Request HTTP header sets are non-varying
*
* @param string $vary Response vary header
* @param array $r1 HTTP header array
* @param array $r2 HTTP header array
*
* @return bool
*/
private function requestsMatch($vary, $r1, $r2)
{
if ($vary) {
foreach (explode(',', $vary) as $header) {
$key = trim(strtolower($header));
$v1 = isset($r1[$key]) ? $r1[$key] : null;
$v2 = isset($r2[$key]) ? $r2[$key] : null;
if ($v1 !== $v2) {
return false;
}
}
}
return true;
}
/**
* Creates an array of cacheable and normalized message headers
*
* @param MessageInterface $message
*
* @return array
*/
private function persistHeaders(MessageInterface $message)
{
// Headers are excluded from the caching (see RFC 2616:13.5.1)
static $noCache = array(
'age' => true,
'connection' => true,
'keep-alive' => true,
'proxy-authenticate' => true,
'proxy-authorization' => true,
'te' => true,
'trailers' => true,
'transfer-encoding' => true,
'upgrade' => true,
'set-cookie' => true,
'set-cookie2' => true
);
// Clone the response to not destroy any necessary headers when caching
$headers = $message->getHeaders()->getAll();
$headers = array_diff_key($headers, $noCache);
// Cast the headers to a string
$headers = array_map(function ($h) { return (string) $h; }, $headers);
return $headers;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Default strategy used to determine of an HTTP request can be cached
*/
class DefaultCanCacheStrategy implements CanCacheStrategyInterface
{
public function canCacheRequest(RequestInterface $request)
{
// Only GET and HEAD requests can be cached
if ($request->getMethod() != RequestInterface::GET && $request->getMethod() != RequestInterface::HEAD) {
return false;
}
// Never cache requests when using no-store
if ($request->hasHeader('Cache-Control') && $request->getHeader('Cache-Control')->hasDirective('no-store')) {
return false;
}
return true;
}
public function canCacheResponse(Response $response)
{
return $response->isSuccessful() && $response->canCache();
}
}

View file

@ -0,0 +1,174 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Exception\BadResponseException;
/**
* Default revalidation strategy
*/
class DefaultRevalidation implements RevalidationInterface
{
/** @var CacheStorageInterface Cache object storing cache data */
protected $storage;
/** @var CanCacheStrategyInterface */
protected $canCache;
/**
* @param CacheStorageInterface $cache Cache storage
* @param CanCacheStrategyInterface $canCache Determines if a message can be cached
*/
public function __construct(CacheStorageInterface $cache, CanCacheStrategyInterface $canCache = null)
{
$this->storage = $cache;
$this->canCache = $canCache ?: new DefaultCanCacheStrategy();
}
public function revalidate(RequestInterface $request, Response $response)
{
try {
$revalidate = $this->createRevalidationRequest($request, $response);
$validateResponse = $revalidate->send();
if ($validateResponse->getStatusCode() == 200) {
return $this->handle200Response($request, $validateResponse);
} elseif ($validateResponse->getStatusCode() == 304) {
return $this->handle304Response($request, $validateResponse, $response);
}
} catch (BadResponseException $e) {
$this->handleBadResponse($e);
}
// Other exceptions encountered in the revalidation request are ignored
// in hopes that sending a request to the origin server will fix it
return false;
}
public function shouldRevalidate(RequestInterface $request, Response $response)
{
if ($request->getMethod() != RequestInterface::GET) {
return false;
}
$reqCache = $request->getHeader('Cache-Control');
$resCache = $response->getHeader('Cache-Control');
$revalidate = $request->getHeader('Pragma') == 'no-cache' ||
($reqCache && ($reqCache->hasDirective('no-cache') || $reqCache->hasDirective('must-revalidate'))) ||
($resCache && ($resCache->hasDirective('no-cache') || $resCache->hasDirective('must-revalidate')));
// Use the strong ETag validator if available and the response contains no Cache-Control directive
if (!$revalidate && !$resCache && $response->hasHeader('ETag')) {
$revalidate = true;
}
return $revalidate;
}
/**
* Handles a bad response when attempting to revalidate
*
* @param BadResponseException $e Exception encountered
*
* @throws BadResponseException
*/
protected function handleBadResponse(BadResponseException $e)
{
// 404 errors mean the resource no longer exists, so remove from
// cache, and prevent an additional request by throwing the exception
if ($e->getResponse()->getStatusCode() == 404) {
$this->storage->delete($e->getRequest());
throw $e;
}
}
/**
* Creates a request to use for revalidation
*
* @param RequestInterface $request Request
* @param Response $response Response to revalidate
*
* @return RequestInterface returns a revalidation request
*/
protected function createRevalidationRequest(RequestInterface $request, Response $response)
{
$revalidate = clone $request;
$revalidate->removeHeader('Pragma')->removeHeader('Cache-Control');
if ($response->getLastModified()) {
$revalidate->setHeader('If-Modified-Since', $response->getLastModified());
}
if ($response->getEtag()) {
$revalidate->setHeader('If-None-Match', $response->getEtag());
}
// Remove any cache plugins that might be on the request to prevent infinite recursive revalidations
$dispatcher = $revalidate->getEventDispatcher();
foreach ($dispatcher->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
if (is_array($listener) && $listener[0] instanceof CachePlugin) {
$dispatcher->removeListener($eventName, $listener);
}
}
}
return $revalidate;
}
/**
* Handles a 200 response response from revalidating. The server does not support validation, so use this response.
*
* @param RequestInterface $request Request that was sent
* @param Response $validateResponse Response received
*
* @return bool Returns true if valid, false if invalid
*/
protected function handle200Response(RequestInterface $request, Response $validateResponse)
{
$request->setResponse($validateResponse);
if ($this->canCache->canCacheResponse($validateResponse)) {
$this->storage->cache($request, $validateResponse);
}
return false;
}
/**
* Handle a 304 response and ensure that it is still valid
*
* @param RequestInterface $request Request that was sent
* @param Response $validateResponse Response received
* @param Response $response Original cached response
*
* @return bool Returns true if valid, false if invalid
*/
protected function handle304Response(RequestInterface $request, Response $validateResponse, Response $response)
{
static $replaceHeaders = array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified');
// Make sure that this response has the same ETag
if ($validateResponse->getEtag() != $response->getEtag()) {
return false;
}
// Replace cached headers with any of these headers from the
// origin server that might be more up to date
$modified = false;
foreach ($replaceHeaders as $name) {
if ($validateResponse->hasHeader($name)) {
$modified = true;
$response->setHeader($name, $validateResponse->getHeader($name));
}
}
// Store the updated response in cache
if ($modified && $this->canCache->canCacheResponse($response)) {
$this->storage->cache($request, $response);
}
return true;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Never performs cache revalidation and just assumes the request is invalid
*/
class DenyRevalidation extends DefaultRevalidation
{
public function __construct() {}
public function revalidate(RequestInterface $request, Response $response)
{
return false;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Cache revalidation interface
*/
interface RevalidationInterface
{
/**
* Performs a cache revalidation
*
* @param RequestInterface $request Request to revalidate
* @param Response $response Response that was received
*
* @return bool Returns true if the request can be cached
*/
public function revalidate(RequestInterface $request, Response $response);
/**
* Returns true if the response should be revalidated
*
* @param RequestInterface $request Request to check
* @param Response $response Response to check
*
* @return bool
*/
public function shouldRevalidate(RequestInterface $request, Response $response);
}

View file

@ -0,0 +1,19 @@
<?php
namespace Guzzle\Plugin\Cache;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Never performs cache revalidation and just assumes the request is still ok
*/
class SkipRevalidation extends DefaultRevalidation
{
public function __construct() {}
public function revalidate(RequestInterface $request, Response $response)
{
return true;
}
}

View file

@ -0,0 +1,28 @@
{
"name": "guzzle/plugin-cache",
"description": "Guzzle HTTP cache plugin",
"homepage": "http://guzzlephp.org/",
"keywords": ["plugin", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2",
"guzzle/http": "self.version",
"guzzle/cache": "self.version"
},
"autoload": {
"psr-0": { "Guzzle\\Plugin\\Cache": "" }
},
"target-dir": "Guzzle/Plugin/Cache",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}