kevinrob / guzzle-cache-middleware Goto Github PK
View Code? Open in Web Editor NEWA HTTP Cache for Guzzle 6. It's a simple Middleware to be added in the HandlerStack.
License: MIT License
A HTTP Cache for Guzzle 6. It's a simple Middleware to be added in the HandlerStack.
License: MIT License
I noticed that every call the cache being updated. Maybe it make sense to introduce different strategies of storing cache? For example, we use Etag as a cache mechanism so it's really useless to update cache everytime server responses with 304
Hello i use your lib to cache my api call into a redis server.
to make it , i use this strategy
$stack = HandlerStack::create(); $stack->push( new CacheMiddleware( new GreedyCacheStrategy( new DoctrineCacheStorage( new PredisCache( new Predis\Client(sprintf('tcp://%s:%s',$client,$port)) ) ),60 ) ), 'predis-cache' ); return $stack;
where $client & $port are information about redis server.
As you see i force a TTL to 60 seconds but it store an unlimited ttl (-1)
Is it normal ?
Thanks in advance
Add a check about request on the fetched object. If we have a collision, the cache entry will not be use.
The simple example provided in the README:
$stack->push(new CacheMiddleware(), 'cache');
does not work without Doctrine installed:
Fatal error: Class undefined: Doctrine\Common\Cache\ArrayCache in vendor/kevinrob/guzzle-cache-middleware/src/Strategy/PrivateCacheStrategy.php on line 61
Suggesting doctrine/cache
seems misleading if the package doesn't work without it.
I noticed a few points that I think could be improved in the PSR-6 storage implementation:
The implementation uses serialize() and unserialize() explicitly. Is this necessary? CacheItemInterface::set()
expects a serializable value, not a serialized string. As a consequence, I think that the CacheEntry
is serialized twice, which has an impact on both performance and storage space. As far as I can see, the calls to serialize()
and unserialize()
could simply be removed.
You use CachePoolInterface::getItem() to retrieve a CacheItemInterface
, on which you can call set()
. As far as I understand it, you're only doing this because you can't create a CacheItemInterface
: it's an interface; so you're getting one from the CacheItemPoolInterface
out of simplicity. But this has a performance impact: you're reading from the cache for nothing. A solution could be to create a very simple CacheItemInterface
implementation that you would instantiate before save()
ing it in the pool.
CacheItemInterface
features an expiresAt() method; it would be nice to set an expiration date-time computed from $data->getTTL()
, similar to the Doctrine Cache implementation.
If I'm mistaken somewhere, please let me know. Otherwise I'll be happy to submit a PR to fix the above points!
Problem:
If you are using Etag
or Last-Modified
validation models - it is a big risk of cache overflow.
E.g.: you are updating response every second and as a result update Last-Modified header every second.
The record will be added to cache storage and never will be removed as it has no TTL.
As Etag header is new - record with a new key will be created.
Probable solution:
Remove headers mentioned above from response headers before generating a cache key. E.g. at PrivateCacheStrategy:123
My CouchDB Server is only sending the following header, but your middleware does not cache the responses.
Please fix ๐
array(6) {
'Server' =>
array(1) {
[0] =>
string(29) "CouchDB/1.6.1 (Erlang OTP/17)"
}
'ETag' =>
array(1) {
[0] =>
string(36) ""1-37046689c0d0d38943cd8b3046852a91""
}
'Date' =>
array(1) {
[0] =>
string(29) "Mon, 06 Jul 2015 20:35:54 GMT"
}
'Content-Type' =>
array(1) {
[0] =>
string(25) "text/plain; charset=utf-8"
}
'Content-Length' =>
array(1) {
[0] =>
string(3) "123"
}
'Cache-Control' =>
array(1) {
[0] =>
string(15) "must-revalidate"
}
}
https://tools.ietf.org/html/rfc7234#section-5.2.1
With Pragma
fallback => https://tools.ietf.org/html/rfc7234#section-5.4
I've a page doing some REST queries using Guzzle 6. It works fine, however sometimes it gets to slow because it's always making queries. I found out that guzzle-cache-middleware is supposed to cache responses from the remote API.
However I can't get it to work, my code follows something like:
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use League\Flysystem\Adapter\Local;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\FlysystemStorage;
(...)
$stack = HandlerStack::create();
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new FlysystemStorage(
new Local("/tmp/sitex")
)
)
),
"cache"
);
// Request
$client = new Client([
"handler" => $stack,
"base_uri" => "http://...,
"timeout" => 2.0,
]);
$response = $client->request("GET", "/posts", [
(...)
After running the code I don't get any errors or warnings. Guzzle still gives me the API response, however nothing new appear into /tmp/sitex
.
Do I need to set anything after the request to cache the response? Are there options like setting the TTL of the responses?
The documentation doesn't explain any of this, so if someone could help me it would be nice :) Thanks!
Is there a simple way that I can invoke an invalidation or removal of the cache for a specific call (e.g. http://api.test.com/users/1).
I need to do this after a PUT request to the api to access the new data on the next GET request.
is it posible to cache POST requests?
thanks in advance
When using the greedy cache we must set the TTL in the constructor. It would be a good idea to make it dynamic (maybe using a custom header in the request ?).
Does the following code work?
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new LaravelCacheStorage(
Cache::store('file')
)
)
),
'cache'
);
Where is the cache saved? Normally in storage/framework/cache but i don't find the cache file.
If the response body is consumed using $response->getBody()->getContents()
, then if another cache hit occurs during the same request, the cache hit will have an already consumed body.
Looking at CacheEntry,
guzzle-cache-middleware/src/CacheEntry.php
Line 232 in ba5994e
How do you set a key and retrieve it? When doing a GET request the cache is created successfully with laravel as Cache|:store('file').
In the LaravelCacheStorage public fetch method, I added this code snippet:
\Log::info('Fetch key @ '. \Carbon\Carbon::now() .' with key: ' . $key);
In the try block of the public fetch method I added this snippet:
\Log::info('Fetch cache @ '. \Carbon\Carbon::now() .' with cache: ' . $cache);
In the LaravelCacheStorage public save method, I added this code snippet:
\Log::info('Save key @ '. \Carbon\Carbon::now() .' with key: ' . $key);
In the try block of the public save method I added this snippet:
\Log::info('Lifetime @ '. \Carbon\Carbon::now() .' with lifetime: ' . $lifeTime);
In the logs, when making a GET request for the first time the fetch method is fired with not results, but a key is logged, then the save method was fired, which then created the cache file successfully, but what is the readable cache key name?
When doing the same GET request the fetch method is fired first again in the logs, and then logs save method which is also fired again.
If I copy the key from the log file, eg; 'eb88a9055662c8786d3400969ee4e407eb56c9cc3a780b9358f7c6bf1e2829e2' and use the LaravelCacheStorage public fetch method to retrieve the key, it fetches the key as expected.
But I can't check to see if a cache key exists, before firing off another GET request, as I don't know what the key name is? Where is key's name set? How does this work, as it does not seem to be using the cache file on consecutive GET requests.
I get "PHP Fatal error: Class 'CacheMiddleware' not found" trying to use your example filecache (which also is missing a ")" at the end) and how do you specify the TTL on the cache?
I am trying to download a file from a file sharing website, but it really slows down the execution of the script. For large files it is giving me a time out error. I tried everything, but I'm still unable to download the cache URL files, as fast as it can be.
`use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
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;
use GuzzleHttp\Psr7;
use GuzzleHttp\Cookie\FileCookieJar;
$current_dir = getcwd();
$temp_dir = "/temp/";
$cache_dir = "/cache/";
$stack = HandlerStack::create();
$stack->push(new CacheMiddleware(
new PrivateCacheStrategy(
new DoctrineCacheStorage(
new ChainCache([
new ArrayCache(),
new FilesystemCache($current_dir . $cache_dir),
])
)
)
), 'cache');
// Guzzle init
$cookieJar = new FileCookieJar($current_dir . $temp_dir .'jar.txt', true);
$client = new Client([
'defaults' => ['debug' => true],
'verify' => $current_dir . $temp_dir ."cacert.pem",
'handler' => $stack,
'cookies' => $cookieJar
]);
$download_file = "http://affix.is/mp3embed-1cg17i8ptsc8.mp3";
$resource = fopen($current_dir . $temp_dir ."temp.ext", 'w');
$response = $client->request('GET', $download_file, ['sink' => $resource]);
echo html_status($response);`
As of now we can only exclude http methods. It would be great also to explicitly tell to not cache 404 (for example). A good idea might be to update this property: https://github.com/Kevinrob/guzzle-cache-middleware/blob/master/src/CacheMiddleware.php#L49 to make it like this:
protected $httpMethods = [
'GET' => [
200,
201,
// etc etc
]
];
It would be something with a default configuration. It could be overriden through strategy configuration and requests specifics.
Using version ~0.6 and the example in the README to use the DoctrineCacheWrapper component.
No matter what I do, I just get the following folder structure and file:
30\a7\1e\5d\66\05\8b\51\2e\b1\11\50\ad\5c\c2\6c\5a\5a\d4\80\fa\a1\c3\08\9a\e2\72\c9\ac\7d\7f\84\DoctrineNamespaceCacheKey[].doctrinecache.data
The file always contains:
0
i:1;
This is when I am GETing resources behind a basic auth web service, which from what I can see is setting the correct cache headers.
< HTTP/1.1 200 OK
< Cache-Control: public
< Content-Type: application/json; charset=utf-8
< Expires: Sat, 05 Sep 2015 15:07:35 GMT
I'm assuming something's not right, but I have no idea what.
PHP 5.5.24 / Apache/2.2.15 (Unix)
Jamie
Is there a way with the current implementation to force the client to cache the response regardless the headers coming from the response? I would like to ask the response coming from an API but it is so badly implemented that it doesn't send back proper headers. What would be the best way to go about it?
In README.md, it currently is:
$stack->push(new CacheMiddleware(new ChainCache([
new ArrayCache(),
new ApcCache(),
new FileCache('/tmp/'),
]), 'cache');
It should be:
$stack->push(new CacheMiddleware(new PrivateCache(new ChainCache([
new ArrayCache(),
new ApcCache(),
new FileCache('/tmp/'),
])), 'cache');
Guzzle 6 has a good support for logging. Configuring HTTP client, you can easily log a lot of details.
When this middleware is used, it's impossible to hook into request / response process and log that request / response was cached.
I need to use Redis cache instead Predis because predis is more slower for multiple calls. Do you have an example to create interface?
Whenever I use this middleware when combined with the sink
option empty files are written:
$client->get($uri, ['sink' => "$dir/$uri"]);
The file $dir/$uri
is created but has no content.
There is a private property $timestampStale
in CacheEntry
that may cause serialize() to fail when extending from CacheEntry
.
CacheEntry
is used by CacheMiddleware
, the following error may occur: serialize(): "timestampStale" returned as member variable from __sleep() but does not exist.
.$timestampStale
to protected
, another error may occur: serialize(): "timestampStale" is returned from __sleep multiple times.
My suggestion is to make $timestampStale
protected in CacheEntry
as I don't see a specific reason why it should be private while the rest are all protected.
Hi i need to use this package with guzzle 6 and etag cache.
Then i need to test if the caching is working.
Can you help me becuause i don't know how to do it.
Thank you in advance.
Does GCM store cache on the user's local machine by default? And then the storage interface build-ins are for server-side/remote caching?
Hey,
Is it possible to invalidate the whole cache with this middleware? Is it even possible to invalidate only the cache that belongs to a certain route or url?
The docs seem to not explain this.
Kind regards,
Stephan
With a custom interface for caching with a implementing of it that take a Doctrine/Cache
in the constructor for compatibility.
GreedyCacheStrategy depends on the response headers Etag and Last-Modified, though it should not.
If Response has any of this headers, then CacheEntry::hasValidationInformation returns true, then CacheEntry::getTTL returns 0, then CacheEntry saved without expiration instead of defined ttl.
Hi, I get this when trying to use the library.
[ErrorException]
Argument 1 passed to Kevinrob\GuzzleCache\CacheMiddleware::__invoke() must be callable, object given, called in /project_path/vendor/guzzlehttp/guz
zle/src/Client.php on line 268 and defined
I think this
public function __invoke(callable $handler)
should be looking like this
public function __invoke(RequestInterface $request, array $optionsr)
What do you think?
https://tools.ietf.org/html/rfc7234#section-4.2.2
For response without explicit expiration times. cf #19
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.