HTTP Caching adaptor class that provides caching services to the Request_Client class, using HTTP cache control logic as defined in RFC 2616.
Class declared in MODPATH/cache/classes/HTTP/Cache.php on line 3.
string(14) "x-cache-status"
string(5) "SAVED"
string(3) "HIT"
string(4) "MISS"
string(12) "x-cache-hits"
boolean
$_allow_private_cacheDefines whether this client should cache private
cache directives
bool FALSE
Cache
$_cachecache driver to use for HTTP caching
NULL
callback
$_cache_key_callbackCache key generator callback
NULL
int
$_request_timeThe timestamp of the request
NULL
int
$_response_timeThe timestamp of the response
NULL
Constructor method for this class. Allows dependency injection of the
required components such as Cache
and the cache key generator.
array
$options
= array(0) - $options public function __construct(array $options = [])
{
foreach ($options as $key => $value) {
if (method_exists($this, $key)) {
$this->$key($value);
}
}
if ($this->_cache_key_callback === null) {
$this->cache_key_callback('HTTP_Cache::basic_cache_key_generator');
}
}
Gets or sets the Request_Client::allow_private_cache setting.
If set to true
, the client will also cache cache-control directives
that have the private
setting.
boolean
$setting
= NULL - Allow caching of privately marked responses boolean
[Request_Client]
public function allow_private_cache($setting = null)
{
if ($setting === null)
return $this->_allow_private_cache;
$this->_allow_private_cache = (bool) $setting;
return $this;
}
Basic cache key generator that hashes the entire request and returns it. This is fine for static content, or dynamic content where user specific information is encoded into the request.
// Generate cache key
$cache_key = HTTP_Cache::basic_cache_key_generator($request);
Request
$request
required - $request string
public static function basic_cache_key_generator(Request $request)
{
$uri = $request->uri();
$query = $request->query();
$headers = $request->headers()->getArrayCopy();
$body = $request->body();
return sha1($uri . '?' . http_build_query($query, null, '&') . '~' . implode('~', $headers) . '~' . $body);
}
Getter and setter for the internal caching engine, used to cache responses if available and valid.
Kohana_Cache
$cache
= NULL - Engine to use for caching Kohana_Cache
Kohana_Request_Client
public function cache(Cache $cache = null)
{
if ($cache === null)
return $this->_cache;
$this->_cache = $cache;
return $this;
}
Sets or gets the cache key generator callback for this caching
class. The cache key generator provides a unique hash based on the
Request
object passed to it.
The default generator is HTTP_Cache::basic_cache_key_generator(), which
serializes the entire HTTP_Request
into a unique sha1 hash. This will
provide basic caching for static and simple dynamic pages. More complex
algorithms can be defined and then passed into HTTP_Cache
using this
method.
// Get the cache key callback
$callback = $http_cache->cache_key_callback();
// Set the cache key callback
$http_cache->cache_key_callback('Foo::cache_key');
// Alternatively, in PHP 5.3 use a closure
$http_cache->cache_key_callback(function (Request $request) {
return sha1($request->render());
});
callback
$callback
= NULL - $callback mixed
public function cache_key_callback($callback = null)
{
if ($callback === null)
return $this->_cache_key_callback;
if (!is_callable($callback))
throw new Kohana_Exception('cache_key_callback must be callable!');
$this->_cache_key_callback = $callback;
return $this;
}
Calculates the total Time To Live based on the specification RFC 2616 cache lifetime rules.
Response
$response
required - Response to evaluate mixed
- TTL value or false if the response should not be cachedpublic function cache_lifetime(Response $response)
{
// Get out of here if this cannot be cached
if (!$this->set_cache($response))
return false;
// Calculate apparent age
if ($date = $response->headers('date')) {
$apparent_age = max(0, $this->_response_time - strtotime($date));
} else {
$apparent_age = max(0, $this->_response_time);
}
// Calculate corrected received age
if ($age = $response->headers('age')) {
$corrected_received_age = max($apparent_age, intval($age));
} else {
$corrected_received_age = $apparent_age;
}
// Corrected initial age
$corrected_initial_age = $corrected_received_age + $this->request_execution_time();
// Resident time
$resident_time = time() - $this->_response_time;
// Current age
$current_age = $corrected_initial_age + $resident_time;
// Prepare the cache freshness lifetime
$ttl = null;
// Cache control overrides
if ($cache_control = $response->headers('cache-control')) {
// Parse the cache control header
$cache_control = HTTP_Header::parse_cache_control($cache_control);
if (isset($cache_control['max-age'])) {
$ttl = $cache_control['max-age'];
}
if (isset($cache_control['s-maxage']) AND isset($cache_control['private']) AND $this->_allow_private_cache) {
$ttl = $cache_control['s-maxage'];
}
if (isset($cache_control['max-stale']) AND ! isset($cache_control['must-revalidate'])) {
$ttl = $current_age + $cache_control['max-stale'];
}
}
// If we have a TTL at this point, return
if ($ttl !== null)
return $ttl;
if ($expires = $response->headers('expires'))
return strtotime($expires) - $current_age;
return false;
}
Caches a Response using the supplied Cache and the key generated by Request_Client::_create_cache_key.
If not response is supplied, the cache will be checked for an existing one that is available.
string
$key
required - The cache key to use Request
$request
required - The HTTP Request Response
$response
= NULL - The HTTP Response mixed
public function cache_response($key, Request $request, Response $response = null)
{
if (!$this->_cache instanceof Cache)
return false;
// Check for Pragma: no-cache
if ($pragma = $request->headers('pragma')) {
if ($pragma == 'no-cache')
return false;
elseif (is_array($pragma) AND in_array('no-cache', $pragma))
return false;
}
// If there is no response, lookup an existing cached response
if ($response === null) {
$response = $this->_cache->get($key);
if (!$response instanceof Response)
return false;
// Do cache hit arithmetic, using fast arithmetic if available
if ($this->_cache instanceof Cache_Arithmetic) {
$hit_count = $this->_cache->increment(HTTP_Cache::CACHE_HIT_KEY . $key);
} else {
$hit_count = $this->_cache->get(HTTP_Cache::CACHE_HIT_KEY . $key);
$this->_cache->set(HTTP_Cache::CACHE_HIT_KEY . $key, ++$hit_count);
}
// Update the header to have correct HIT status and count
$response->headers(HTTP_Cache::CACHE_STATUS_KEY, HTTP_Cache::CACHE_STATUS_HIT)
->headers(HTTP_Cache::CACHE_HIT_KEY, $hit_count);
return $response;
} else {
if (($ttl = $this->cache_lifetime($response)) === false)
return false;
$response->headers(HTTP_Cache::CACHE_STATUS_KEY, HTTP_Cache::CACHE_STATUS_SAVED);
// Set the hit count to zero
$this->_cache->set(HTTP_Cache::CACHE_HIT_KEY . $key, 0);
return $this->_cache->set($key, $response, $ttl);
}
}
Creates a cache key for the request to use for caching Kohana_Response returned by Request::execute.
This is the default cache key generating logic, but can be overridden by setting HTTP_Cache::cache_key_callback().
Request
$request
required - Request to create key for callback
$callback
= bool FALSE - Optional callback to use instead of built-in method string
public function create_cache_key(Request $request, $callback = false)
{
if (is_callable($callback))
return call_user_func($callback, $request);
else
return HTTP_Cache::basic_cache_key_generator($request);
}
Executes the supplied Request with the supplied Request_Client.
Before execution, the HTTP_Cache adapter checks the request type,
destructive requests such as POST
, PUT
and DELETE
will bypass
cache completely and ensure the response is not cached. All other
Request methods will allow caching, if the rules are met.
Request_Client
$client
required - Client to execute with Cache-Control Request
$request
required - Request to execute with client unknown
$response
required [Response]
public function execute(Request_Client $client, Request $request, Response $response)
{
if (!$this->_cache instanceof Cache)
return $client->execute_request($request, $response);
// If this is a destructive request, by-pass cache completely
if (in_array($request->method(), [
HTTP_Request::POST,
HTTP_Request::PUT,
HTTP_Request::DELETE
])) {
// Kill existing caches for this request
$this->invalidate_cache($request);
$response = $client->execute_request($request, $response);
$cache_control = HTTP_Header::create_cache_control([
'no-cache',
'must-revalidate'
]);
// Ensure client respects destructive action
return $response->headers('cache-control', $cache_control);
}
// Create the cache key
$cache_key = $this->create_cache_key($request, $this->_cache_key_callback);
// Try and return cached version
if (($cached_response = $this->cache_response($cache_key, $request)) instanceof Response)
return $cached_response;
// Start request time
$this->_request_time = time();
// Execute the request with the Request client
$response = $client->execute_request($request, $response);
// Stop response time
$this->_response_time = (time() - $this->_request_time);
// Cache the response
$this->cache_response($cache_key, $request, $response);
$response->headers(HTTP_Cache::CACHE_STATUS_KEY, HTTP_Cache::CACHE_STATUS_MISS);
return $response;
}
Factory method for HTTP_Cache that provides a convenient dependency injector for the Cache library.
// Create HTTP_Cache with named cache engine
$http_cache = HTTP_Cache::factory('memcache', ['allow_private_cache' => false]);
// Create HTTP_Cache with supplied cache engine
$http_cache = HTTP_Cache::factory(Cache::instance('memcache'), ['allow_private_cache' => false]);
mixed
$cache
required - Cache engine to use array
$options
= array(0) - Options to set to this class HTTP_Cache
public static function factory($cache, array $options = [])
{
if (!$cache instanceof Cache) {
$cache = Cache::instance($cache);
}
$options['cache'] = $cache;
return new HTTP_Cache($options);
}
Invalidate a cached response for the Request supplied. This has the effect of deleting the response from the Cache entry.
Request
$request
required - Response to remove from cache void
public function invalidate_cache(Request $request)
{
if (($cache = $this->cache()) instanceof Cache) {
$cache->delete($this->create_cache_key($request, $this->_cache_key_callback));
}
return;
}
Returns the duration of the last request execution.
Either returns the time of completed requests or
false
if the request hasn't finished executing, or
is yet to be run.
mixed
public function request_execution_time()
{
if ($this->_request_time === null OR $this->_response_time === null)
return false;
return $this->_response_time - $this->_request_time;
}
Controls whether the response can be cached. Uses HTTP protocol to determine whether the response can be cached.
Response
$response
required - The Response boolean
public function set_cache(Response $response)
{
$headers = $response->headers()->getArrayCopy();
if ($cache_control = Arr::get($headers, 'cache-control')) {
// Parse the cache control
$cache_control = HTTP_Header::parse_cache_control($cache_control);
// If the no-cache or no-store directive is set, return
if (array_intersect($cache_control, ['no-cache', 'no-store']))
return false;
// Check for private cache and get out of here if invalid
if (!$this->_allow_private_cache AND in_array('private', $cache_control)) {
if (!isset($cache_control['s-maxage']))
return false;
// If there is a s-maxage directive we can use that
$cache_control['max-age'] = $cache_control['s-maxage'];
}
// Check that max-age has been set and if it is valid for caching
if (isset($cache_control['max-age']) AND $cache_control['max-age'] < 1)
return false;
}
if ($expires = Arr::get($headers, 'expires') AND ! isset($cache_control['max-age'])) {
// Can't cache things that have expired already
if (strtotime($expires) <= time())
return false;
}
return true;
}