A HTTP Cache for Guzzle 6+. It's a simple Middleware to be added in the HandlerStack.
- RFC 7234 compliance
- Performance and transparency
- Assured compatibility with PSR-7
composer require kevinrob/guzzle-cache-middleware
or add it the your composer.json
and run composer update kevinrob/guzzle-cache-middleware
.
Performance. It's very common to do some HTTP calls to an API for rendering a page and it takes times to do it.
With a simple Middleware added at the top of the HandlerStack
of Guzzle.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
// Create default HandlerStack
$stack = HandlerStack::create();
// Add this middleware to the top with `push`
$stack->push(new CacheMiddleware(), 'cache');
// Initialize the client with the handler option
$client = new Client(['handler' => $stack]);
You can use a cache from Doctrine/Cache
:
[...]
use Doctrine\Common\Cache\FilesystemCache;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
[...]
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new DoctrineCacheStorage(
new FilesystemCache('/tmp/')
)
)
),
'cache'
);
You can use ChainCache
for using multiple CacheProvider
instances. With that provider, you have to sort the different caches from the faster to the slower. Like that, you can have a very fast cache.
[...]
use Doctrine\Common\Cache\ChainCache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\FilesystemCache;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
[...]
$stack->push(new CacheMiddleware(
new PrivateCacheStrategy(
new DoctrineCacheStorage(
new ChainCache([
new ArrayCache(),
new FilesystemCache('/tmp/'),
])
)
)
), 'cache');
You can use a cache with Laravel, e.g. Redis, Memcache etc.:
[...]
use Illuminate\Support\Facades\Cache;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\LaravelCacheStorage;
[...]
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new LaravelCacheStorage(
Cache::store('redis')
)
)
),
'cache'
);
[...]
use League\Flysystem\Adapter\Local;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\FlysystemStorage;
[...]
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new FlysystemStorage(
new Local('/path/to/cache')
)
)
),
'cache'
);
[...]
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\WordPressObjectCacheStorage;
[...]
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new WordPressObjectCacheStorage()
)
),
'cache'
);
It's possible to add a public shared cache to the stack:
[...]
use Doctrine\Common\Cache\FilesystemCache;
use Doctrine\Common\Cache\PredisCache;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Strategy\PublicCacheStrategy;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
[...]
// Private caching
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new DoctrineCacheStorage(
new FilesystemCache('/tmp/')
)
)
),
'private-cache'
);
// Public caching
$stack->push(
new CacheMiddleware(
new PublicCacheStrategy(
new DoctrineCacheStorage(
new PredisCache(
new Predis\Client('tcp://10.0.0.1:6379')
)
)
)
),
'shared-cache'
);
In some cases servers might send insufficient or no caching headers at all. Using the greedy caching strategy allows defining an expiry TTL on your own while disregarding any possibly present caching headers:
[...]
use Kevinrob\GuzzleCache\KeyValueHttpHeader;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
use Doctrine\Common\Cache\FilesystemCache;
[...]
// Greedy caching
$stack->push(
new CacheMiddleware(
new GreedyCacheStrategy(
new DoctrineCacheStorage(
new FilesystemCache('/tmp/')
),
1800, // the TTL in seconds
new KeyValueHttpHeader(['Authorization']) // Optional - specify the headers that can change the cache key
)
),
'greedy-cache'
);
Because your client may call different apps, on different domains, you may need to define which strategy is suitable to your requests.
To solve this, all you have to do is to define a default cache strategy, and override it by implementing your own Request Matchers.
Here's an example:
namespace App\RequestMatcher;
use Kevinrob\GuzzleCache\Strategy\Delegate\RequestMatcherInterface;
use Psr\Http\Message\RequestInterface;
class ExampleOrgRequestMatcher implements RequestMatcherInterface
{
/**
* @inheritDoc
*/
public function matches(RequestInterface $request)
{
return false !== strpos($request->getUri()->getHost(), 'example.org');
}
}
namespace App\RequestMatcher;
use Kevinrob\GuzzleCache\Strategy\Delegate\RequestMatcherInterface;
use Psr\Http\Message\RequestInterface;
class TwitterRequestMatcher implements RequestMatcherInterface
{
/**
* @inheritDoc
*/
public function matches(RequestInterface $request)
{
return false !== strpos($request->getUri()->getHost(), 'twitter.com');
}
}
require_once __DIR__ . '/vendor/autoload.php';
use App\RequestMatcher\ExampleOrgRequestMatcher;
use App\RequestMatcher\TwitterRequestMatcher;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Strategy;
$strategy = new Strategy\Delegate\DelegatingCacheStrategy($defaultStrategy = new Strategy\NullCacheStrategy());
$strategy->registerRequestMatcher(new ExampleOrgRequestMatcher(), new Strategy\PublicCacheStrategy());
$strategy->registerRequestMatcher(new TwitterRequestMatcher(), new Strategy\PrivateCacheStrategy());
$stack = HandlerStack::create();
$stack->push(new CacheMiddleware($strategy));
$guzzle = new Client(['handler' => $stack]);
With this example:
- All requests to
example.org
will be handled byPublicCacheStrategy
- All requests to
twitter.com
will be handled byPrivateCacheStrategy
- All other requests won't be cached.
See Guzzle Cache module.
- Speeding Up APIs/Apps/Smart Toasters with HTTP Response Caching
- Caching HTTP-Requests with Guzzle 6 and PSR-6
make init
make test
make shell