* @license http://opensource.org/licenses/gpl-license.php GNU Public License * @copyright ©2006 l.m.orchard * @link http://decafbad.com/trac/wiki/FeedMagick * @version 0.0 */ /** * Required classes */ require_once('Snoopy.class.php'); /* * CONSTANTS - redefine these in your config. * * HTTP_CACHE_ROOT - Root path where all cached HTTP requests go. * */ if ( !defined('HTTP_CACHE_ROOT') ) { define('HTTP_CACHE_ROOT', 'http-cache'); } if ( !defined('HTTP_CACHE_USE_CACHE') ) { define('HTTP_CACHE_USE_CACHE', true); } if ( !defined('HTTP_CACHE_USE_GZIP') ) { define('HTTP_CACHE_USE_GZIP', true); } if ( !defined('HTTP_CACHE_USER_AGENT') ) { $ua = 'FeedMagick/0.0 (+http://decafbad.com/trac/wiki/FeedMagick)'; define('HTTP_CACHE_USER_AGENT', $ua); } if ( !defined('HTTP_CACHE_MAX_AGE') ) { define('HTTP_CACHE_MAX_AGE', 3600); } /** * HTTPCache implements an HTTP cache for feeds using conditional GET * * This class is a bit inspired by Joe Gregorio's httpcache.py [1] in * Python and uses bits stolen from MagpieRSS [2]. * * [1]: http://bitworking.org/projects/httpcache/ * [2]: http://magpierss.sourceforge.net/ * * Synopsis: * * $cache =& new HTTPCache('http://example.com'); * $data = $cache->fetch(); * if ($cache->fresh) { * print "The data hasn't changed!"; * } * */ class HTTPCache { var $client; var $content; var $fresh; var $url; var $headers; var $cache_id; var $cache_path; var $cache_entry; var $cache_root = HTTP_CACHE_ROOT; function HTTPCache($url) { $this->content = null; $this->fresh = null; $this->url = $url; $this->cache_entry = array(); $this->headers = array(); $this->max_age = HTTP_CACHE_MAX_AGE; } function fetch() { global $log; // If USE_CACHE is false, circumvent caching altogether. if (!HTTP_CACHE_USE_CACHE) { $log->debug("Disabled cache fetch of ".$this->url); $this->_fetchURL(); return $this->content; } $this->cache_id = md5($this->url); $this->cache_path = $this->cache_root."/".$this->cache_id; if ($this->_loadCacheEntry()) { if (array_key_exists('etag', $this->cache_entry)) { $this->headers['If-None-Match'] = $this->cache_entry['etag']; } if (array_key_exists('last_modified', $this->cache_entry)) { $this->headers['If-Last-Modified'] = $this->cache_entry['last_modified']; } // TODO: Streamline this age check into the 304 check below? // Check cache entry age $mtime = filemtime($this->cache_path); $age = time() - $mtime; if ( $age < $this->max_age ) { $log->debug("Still-fresh cache hit of ".$this->url); $this->fresh = true; $this->content = $this->cache_entry['content']; return $this->content; } } $log->debug("Cache miss and fetch of ".$this->url); $this->_fetchURL(); if ( ('304' == $this->client->status) && (array_key_exists('content', $this->cache_entry)) ) { $this->fresh = true; $this->content = $this->cache_entry['content']; } elseif ($this->is_success($this->client->status)) { $this->fresh = false; $this->content = $this->client->results; $this->_cacheResponseHeaders(); } // elseif ($this->is_error($this->client->status)) { } $this->_saveCacheEntry(); return $this->content; } function _cacheResponseHeaders() { // find Etag, and Last-Modified foreach($this->client->headers as $h) { // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1" if (strpos($h, ": ")) { list($field, $val) = explode(": ", $h, 2); } else { $field = $h; $val = ""; } if ( $field == 'ETag' ) { $this->cache_entry['etag'] = $val; } if ( $field == 'Last-Modified' ) { $this->cache_entry['last_modified'] = $val; } } } function _loadCacheEntry() { $fn = $this->cache_path; if ( file_exists($fn) && ($fin = fopen($fn,'r')) ) { $data = fread($fin, filesize($fn)); $this->cache_entry = unserialize($data); fclose($fin); return true; } return false; } function _saveCacheEntry() { // TODO: Report error if HTTPCache cache file not writable /* if (!is_writable($this->cache_path)) { error_log("Cache entry not writeable: ".$this->cache_path); return; } */ if ($fout = fopen($this->cache_path,'w')) { $this->cache_entry['content'] = $this->content; fwrite($fout, serialize($this->cache_entry)); fclose($fout); } } function _fetchURL() { // Snoopy is an HTTP client in PHP $this->client = new Snoopy(); $this->client->agent = HTTP_CACHE_USER_AGENT; $this->client->use_gzip = HTTP_CACHE_USE_GZIP; if (is_array($this->headers) ) { $this->client->rawheaders = $this->headers; } @$this->client->fetch($this->url); return $this->client; } function is_success ($sc) { return $sc >= 200 && $sc < 300; } function is_error ($sc) { return $sc >= 400 && $sc < 600; } function is_info ($sc) { return $sc >= 100 && $sc < 200; } function is_redirect ($sc) { return $sc >= 300 && $sc < 400; } function is_client_error ($sc) { return $sc >= 400 && $sc < 500; } function is_server_error ($sc) { return $sc >= 500 && $sc < 600; } } ?>