sonarrradarrplexorganizrnginxdashboardmuximuxlandingpagestartpagelandinghtpcserverhomepagesabnzbdheimdallembycouchpotatonzbgetbookmarkapplication-dashboard
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
7.1 KiB
241 lines
7.1 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of Cache Plugin.
|
|
*
|
|
* (c) Graham Campbell <graham@alt-three.com>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace GrahamCampbell\CachePlugin;
|
|
|
|
use Exception;
|
|
use Http\Client\Common\Plugin;
|
|
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator;
|
|
use Http\Client\Common\Plugin\Cache\Generator\HeaderCacheKeyGenerator;
|
|
use Http\Client\Common\Plugin\Exception\RewindStreamException;
|
|
use Http\Client\Common\Plugin\VersionBridgePlugin;
|
|
use Http\Message\StreamFactory;
|
|
use Psr\Cache\CacheItemInterface;
|
|
use Psr\Cache\CacheItemPoolInterface;
|
|
use Psr\Http\Message\RequestInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
/**
|
|
* This is the response cache plugin class.
|
|
*
|
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
|
* @author Graham Campbell <graham@alt-three.com>
|
|
*/
|
|
class CachePlugin implements Plugin
|
|
{
|
|
use VersionBridgePlugin;
|
|
|
|
/**
|
|
* The cache item pool instance.
|
|
*
|
|
* @var \Psr\Cache\CacheItemPoolInterface
|
|
*/
|
|
protected $pool;
|
|
|
|
/**
|
|
* The steam factory instance.
|
|
*
|
|
* @var \Http\Message\StreamFactory
|
|
*/
|
|
protected $streamFactory;
|
|
|
|
/**
|
|
* The cache key generator instance.
|
|
*
|
|
* @var \Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator
|
|
*/
|
|
protected $generator;
|
|
|
|
/**
|
|
* The cache lifetime in seconds.
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $lifetime;
|
|
|
|
/**
|
|
* Create a new cache plugin.
|
|
*
|
|
* @param \Psr\Cache\CacheItemPoolInterface $pool
|
|
* @param \Http\Message\StreamFactory $streamFactory
|
|
* @param \Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator|null $generator
|
|
* @param int|null $lifetime
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, CacheKeyGenerator $generator = null, int $lifetime = null)
|
|
{
|
|
$this->pool = $pool;
|
|
$this->streamFactory = $streamFactory;
|
|
$this->generator = $generator ?: new HeaderCacheKeyGenerator(['Authorization', 'Cookie', 'Accept', 'Content-type']);
|
|
$this->lifetime = $lifetime ?: 3600 * 48;
|
|
}
|
|
|
|
/**
|
|
* Handle the request and return the response coming from the next callable.
|
|
*
|
|
* @param \Psr\Http\Message\RequestInterface $request
|
|
* @param callable $next
|
|
* @param callable $first
|
|
*
|
|
* @return \Http\Promise\Promise
|
|
*/
|
|
protected function doHandleRequest(RequestInterface $request, callable $next, callable $first)
|
|
{
|
|
$method = strtoupper($request->getMethod());
|
|
// If the request not is cachable, move to $next
|
|
if (!in_array($method, ['GET', 'HEAD'], true)) {
|
|
return $next($request);
|
|
}
|
|
|
|
$cacheItem = $this->createCacheItem($request);
|
|
|
|
if ($cacheItem->isHit() && ($etag = $this->getETag($cacheItem))) {
|
|
$request = $request->withHeader('If-None-Match', $etag);
|
|
}
|
|
|
|
return $next($request)->then(function (ResponseInterface $response) use ($cacheItem) {
|
|
if (304 === $response->getStatusCode()) {
|
|
if (!$cacheItem->isHit()) {
|
|
// We do not have the item in cache. This plugin did not
|
|
// add If-None-Match headers. Return the response.
|
|
return $response;
|
|
}
|
|
|
|
// The cached response we have is still valid
|
|
$cacheItem->set($cacheItem->get())->expiresAfter($this->lifetime);
|
|
$this->pool->save($cacheItem);
|
|
|
|
return $this->createResponseFromCacheItem($cacheItem);
|
|
}
|
|
|
|
if ($this->isCacheable($response)) {
|
|
$bodyStream = $response->getBody();
|
|
$body = $bodyStream->__toString();
|
|
if ($bodyStream->isSeekable()) {
|
|
$bodyStream->rewind();
|
|
} else {
|
|
$response = $response->withBody($this->streamFactory->createStream($body));
|
|
}
|
|
|
|
$cacheItem
|
|
->expiresAfter($this->lifetime)
|
|
->set([
|
|
'response' => $response,
|
|
'body' => $body,
|
|
'etag' => $response->getHeader('ETag'),
|
|
]);
|
|
$this->pool->save($cacheItem);
|
|
}
|
|
|
|
return $response;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a cache item for a request.
|
|
*
|
|
* @param \Psr\Http\Message\RequestInterface $request
|
|
*
|
|
* @return \Psr\Cache\CacheItemInterface
|
|
*/
|
|
protected function createCacheItem(RequestInterface $request)
|
|
{
|
|
$key = sha1($this->generator->generate($request));
|
|
|
|
return $this->pool->getItem($key);
|
|
}
|
|
|
|
/**
|
|
* Verify that we can cache this response.
|
|
*
|
|
* @param \Psr\Http\Message\ResponseInterface $response
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function isCacheable(ResponseInterface $response)
|
|
{
|
|
if (!in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 404, 410])) {
|
|
return false;
|
|
}
|
|
|
|
return !$this->getCacheControlDirective($response, 'no-cache');
|
|
}
|
|
|
|
/**
|
|
* Get the value of a parameter in the cache control header.
|
|
*
|
|
* @param \Psr\Http\Message\ResponseInterface $response
|
|
* @param string $name
|
|
*
|
|
* @return bool|string
|
|
*/
|
|
protected function getCacheControlDirective(ResponseInterface $response, string $name)
|
|
{
|
|
foreach ($response->getHeader('Cache-Control') as $header) {
|
|
if (preg_match(sprintf('|%s=?([0-9]+)?|i', $name), $header, $matches)) {
|
|
// return the value for $name if it exists
|
|
if (isset($matches[1])) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create a response from a cache item.
|
|
*
|
|
* @param \Psr\Cache\CacheItemInterface $cacheItem
|
|
*
|
|
* @return \Psr\Http\Message\ResponseInterface
|
|
*/
|
|
protected function createResponseFromCacheItem(CacheItemInterface $cacheItem)
|
|
{
|
|
$data = $cacheItem->get();
|
|
|
|
$response = $data['response'];
|
|
$stream = $this->streamFactory->createStream($data['body']);
|
|
|
|
try {
|
|
$stream->rewind();
|
|
} catch (Exception $e) {
|
|
throw new RewindStreamException('Cannot rewind stream.', 0, $e);
|
|
}
|
|
|
|
$response = $response->withBody($stream);
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Get the ETag from the cached response.
|
|
*
|
|
* @param \Psr\Cache\CacheItemInterface $cacheItem
|
|
*
|
|
* @return string|null
|
|
*/
|
|
protected function getETag(CacheItemInterface $cacheItem)
|
|
{
|
|
$data = $cacheItem->get();
|
|
|
|
foreach ($data['etag'] as $etag) {
|
|
if (!empty($etag)) {
|
|
return $etag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|