diff --git a/advanced-cache.php b/advanced-cache.php index 00309eb..7d3ea09 100644 --- a/advanced-cache.php +++ b/advanced-cache.php @@ -12,6 +12,7 @@ function batcache_cancel() { class batcache { // This is the base configuration. You can edit these variables or move them into your wp-config.php file. var $max_age = 300; // Expire batcache items aged this many seconds (zero to disable batcache) + var $stale_if_error = 0; // If not zero, will set stale-if-error extension for Cache-Control (e.g. for Squid) var $remote = 0; // Zero disables sending buffers to remote datacenters (req/sec is never sent) @@ -24,6 +25,10 @@ class batcache { var $headers = array(); // Add headers here. These will be sent with every response from the cache. + var $cache_redirects = false; // Set true to enable redirect caching. + var $redirect_status = false; // This is set to the response code during a redirect. + var $redirect_location = false; // This is set to the redirect location. + var $uncached_headers = array('transfer-encoding'); // These headers will never be cached. Apply strtolower. var $debug = true; // Set false to hide the batcache info @@ -32,19 +37,55 @@ class batcache { var $cancel = false; // Change this to cancel the output buffer. Use batcache_cancel(); - var $do = false; // By default, we do not cache + var $genlock; // Used internally + var $do; // Used internally function batcache( $settings ) { if ( is_array( $settings ) ) foreach ( $settings as $k => $v ) $this->$k = $v; } + function is_ssl() { + if ( isset($_SERVER['HTTPS']) ) { + if ( 'on' == strtolower($_SERVER['HTTPS']) ) + return true; + if ( '1' == $_SERVER['HTTPS'] ) + return true; + } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) { + return true; + } + return false; + } + function status_header( $status_header ) { $this->status_header = $status_header; return $status_header; } + function redirect_status( $status, $location ) { + if ( $this->cache_redirects ) { + $this->redirect_status = $status; + $this->redirect_location = $location; + } + + return $status; + } + + function get_cache_control_directives() { + $directives = array(); + $max_age = $this->max_age; + if ( !empty($this->cache['time']) ) { + $max_age = max(0, $max_age - time() + $this->cache['time']); + } + $directives[] = sprintf( 'max-age=%d', $max_age ); + $directives[] = 'must-revalidate'; + if ( $this->stale_if_error > 0 ) { + $directives[] = sprintf( 'stale-if-error=%d', $this->stale_if_error ); + } + return $directives; + } + function configure_groups() { // Configure the memcached client if ( ! $this->remote ) @@ -78,9 +119,9 @@ function ob($output) { // Remember, $wp_object_cache was clobbered in wp-settings.php so we have to repeat this. $this->configure_groups(); - // Do not batcache blank pages (usually they are HTTP redirects) + // Do not batcache blank pages unless they are HTTP redirects $output = trim($output); - if ( empty($output) ) + if ( $output === '' && (!$this->redirect_status || !$this->redirect_location) ) return; // Construct and save the batcache @@ -89,12 +130,22 @@ function ob($output) { 'time' => time(), 'timer' => $this->timer_stop(false, 3), 'status_header' => $this->status_header, + 'redirect_status' => $this->redirect_status, + 'redirect_location' => $this->redirect_location, 'version' => $this->url_version ); - if ( function_exists( 'apache_response_headers' ) ) { + if ( function_exists( 'headers_list' ) ) { + foreach ( headers_list() as $header ) { + list($k, $v) = array_map('trim', explode(':', $header, 2)); + $cache['headers'][$k] = $v; + } + } elseif ( function_exists( 'apache_response_headers' ) ) { $cache['headers'] = apache_response_headers(); - if ( !empty( $this->uncached_headers ) ) foreach ( $cache['headers'] as $header => $value ) { + } + + if ( $cache['headers'] && !empty( $this->uncached_headers ) ) { + foreach ( $cache['headers'] as $header => $value ) { if ( in_array( strtolower( $header ), $this->uncached_headers ) ) unset( $cache['headers'][$header] ); } @@ -106,8 +157,8 @@ function ob($output) { wp_cache_delete("{$this->url_key}_genlock", $this->group); if ( $this->cache_control ) { - header('Last-Modified: ' . date('r', $cache['time']), true); - header("Cache-Control: max-age=$this->max_age, must-revalidate", false); + header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $cache['time'] ) . ' GMT', true ); + header('Cache-Control: ' . join(', ', $this->get_cache_control_directives()), false); } if ( !empty($this->headers) ) foreach ( $this->headers as $k => $v ) { @@ -181,8 +232,10 @@ function ob($output) { */ /* Example: batcache everything on this host regardless of traffic level -if ( $_SERVER['HTTP_HOST'] == 'always-batcache-me.com' ) - return; +if ( $_SERVER['HTTP_HOST'] == 'always-batcache-me.com' ) { + $batcache->max_age = 600; // Cache for 10 minutes + $batcache->seconds = $batcache->times = 0; // No need to wait till n number of people have accessed the page, cache instantly +} */ /* Example: If you sometimes serve variants dynamically (e.g. referrer search term highlighting) you probably don't want to batcache those variants. Remember this code is run very early in wp-settings.php so plugins are not yet loaded. You will get a fatal error if you try to call an undefined function. Either include your plugin now or define a test function in this file. @@ -206,11 +259,15 @@ function ob($output) { parse_str($_SERVER['QUERY_STRING'], $batcache->query); $batcache->keys = array( 'host' => $_SERVER['HTTP_HOST'], + 'method' => $_SERVER['REQUEST_METHOD'], 'path' => ( $batcache->pos = strpos($_SERVER['REQUEST_URI'], '?') ) ? substr($_SERVER['REQUEST_URI'], 0, $batcache->pos) : $_SERVER['REQUEST_URI'], 'query' => $batcache->query, 'extra' => $batcache->unique ); +if ( $batcache->is_ssl() ) + $batcache->keys['ssl'] = true; + $batcache->configure_groups(); // Generate the batcache key @@ -246,13 +303,47 @@ function ob($output) { // If the document has been updated and we are the first to notice, regenerate it. if ( $batcache->do !== false && isset($batcache->cache['version']) && $batcache->cache['version'] < $batcache->url_version ) $batcache->genlock = wp_cache_add("{$batcache->url_key}_genlock", 1, $batcache->group); -else $batcache->genlock = 0; // Did we find a batcached page that hasn't expired? if ( isset($batcache->cache['time']) && ! $batcache->genlock && time() < $batcache->cache['time'] + $batcache->max_age ) { + // Issue redirect if cached and enabled + if ( $batcache->cache['redirect_status'] && $batcache->cache['redirect_location'] && $batcache->cache_redirects ) { + $status = $batcache->cache['redirect_status']; + $location = $batcache->cache['redirect_location']; + // From vars.php + $is_IIS = (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'ExpressionDevServer') !== false); + if ( $is_IIS ) { + header("Refresh: 0;url=$location"); + } else { + if ( php_sapi_name() != 'cgi-fcgi' ) { + $texts = array( + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + ); + $protocol = $_SERVER["SERVER_PROTOCOL"]; + if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol ) + $protocol = 'HTTP/1.0'; + if ( isset($texts[$status]) ) + header("$protocol $status " . $texts[$status]); + else + header("$protocol 302 Found"); + } + header("Location: $location"); + } + exit; + } + // Issue "304 Not Modified" only if the dates match exactly. if ( $batcache->cache_control && isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ) { $since = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); + if ( isset($batcache->cache['headers']['Last-Modified']) ) + $batcache->cache['time'] = strtotime( $batcache->cache['headers']['Last-Modified'] ); if ( $batcache->cache['time'] == $since ) { header('Last-Modified: ' . $_SERVER['HTTP_IF_MODIFIED_SINCE'], true, 304); exit; @@ -261,8 +352,8 @@ function ob($output) { // Use the batcache save time for Last-Modified so we can issue "304 Not Modified" if ( $batcache->cache_control ) { - header('Last-Modified: ' . date('r', $batcache->cache['time']), true); - header('Cache-Control: max-age=' . ($batcache->max_age - time() + $batcache->cache['time']) . ', must-revalidate', true); + header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $batcache->cache['time'] ) . ' GMT', true ); + header( 'Cache-Control: ' . join(', ', $batcache->get_cache_control_directives()), true ); } // Add some debug info just before @@ -295,6 +386,7 @@ function ob($output) { return; $wp_filter['status_header'][10]['batcache'] = array( 'function' => array(&$batcache, 'status_header'), 'accepted_args' => 1 ); +$wp_filter['wp_redirect_status'][10]['batcache'] = array( 'function' => array(&$batcache, 'redirect_status'), 'accepted_args' => 2 ); ob_start(array(&$batcache, 'ob')); diff --git a/batcache.php b/batcache.php index 23ef8d3..0dba97a 100644 --- a/batcache.php +++ b/batcache.php @@ -5,7 +5,7 @@ Description: This optional plugin improves Batcache. Author: Andy Skelton Author URI: http://andyskelton.com/ -Version: 1.0 +Version: 1.2 */ // Do not load if our advanced-cache.php isn't loaded diff --git a/readme.txt b/readme.txt index 126ae84..ff32006 100644 --- a/readme.txt +++ b/readme.txt @@ -1,15 +1,15 @@ === Batcache === -Contributors: andy -Tags: cache, memcached, speed, performance, digg -Requires at least: 2.0 -Tested up to: 3.3.1 -Stable tag: 1.0 +Contributors: automattic, andy, orensol, markjaquith, vnsavage, batmoo, yoavf +Tags: cache, memcache, memcached, speed, performance, load, server +Requires at least: 3.2 +Tested up to: 3.5 +Stable tag: 1.2 Batcache uses Memcached to store and serve rendered pages. == Description == -Batcache uses Memcached to store and serve rendered pages. It's not as fast as Donncha's WP-Super-Cache but it can be used where file-based caching is not practical or not desired. +Batcache uses Memcached to store and serve rendered pages. It can also optionally cache redirects. It's not as fast as Donncha's WP-Super-Cache but it can be used where file-based caching is not practical or not desired. For instance, any site that is run on more than one server should use Batcache because it allows all servers to use the same storage. Development testing showed a 40x reduction in page generation times: pages generated in 200ms were served from the cache in 5ms. Traffic simulations with Siege demonstrate that WordPress can handle up to twenty times more traffic with Batcache installed. @@ -26,27 +26,23 @@ Possible future features: 1. Get the Memcached backend working. See below. -2. Upload `advanced-cache.php` to the `/wp-content/` directory +1. Upload `advanced-cache.php` to the `/wp-content/` directory -3. Add this line the top of `wp-config.php` to activate Batcache: +1. Add this line the top of `wp-config.php` to activate Batcache: `define('WP_CACHE', true);` -4. Test by reloading a page in your browser several times and then viewing the source. Just above the `` closing tag you should see some Batcache stats. +1. Test by reloading a page in your browser several times and then viewing the source. Just above the `` closing tag you should see some Batcache stats. -5. Tweak the options near the top of `advanced-cache.php` +1. Tweak the options near the top of `advanced-cache.php` -6. *Optional* Upload `batcache.php` to the `/wp-content/plugins/` directory. - -7. *Optional* Allow for Mobile User Agent Detection: - -Uncomment `$batcache->unique['mobile'] = is_mobile_user_agent();` And add a function called "is_mobile_user_agent" above your call to `define('WP_CACHE', true);` in `wp-config.php` (example function here: http://pastie.org/3239778). +1. *Optional* Upload `batcache.php` to the `/wp-content/plugins/` directory. = Memcached backend = 1. Install [memcached](http://danga.com/memcached) on at least one server. Note the connection info. The default is `127.0.0.1:11211`. -2. Install the [PECL memcached extension](http://pecl.php.net/package/memcache) and [Ryan's Memcached backend 2.0](http://svn.wp-plugins.org/memcached/trunk/). Use the [1.0 branch](http://svn.wp-plugins.org/memcached/branches/1.0/) if you don't have or can't install the PECL extension. +1. Install the [PECL memcached extension](http://pecl.php.net/package/memcache) and [Ryan's Memcached backend 2.0](http://svn.wp-plugins.org/memcached/trunk/). Use the [1.0 branch](http://svn.wp-plugins.org/memcached/branches/1.0/) if you don't have or can't install the PECL extension. == Frequently Asked Questions == @@ -64,3 +60,10 @@ Actually all of WordPress.com stays up during Apple events because of Batcache. Batcache was named "supercache" when it was written. (It's still called that on WordPress.com.) A few months later, while "supercache" was still private, Donncha released the WP-Super-Cache plugin. It wouldn't be fun to dispute the name or create confusion for users so a name change seemed best. The move from "Super" to "Bat" was inspired by comic book heroes. It has nothing to do with the fact that the author's city is home to the [world's largest urban bat colony](http://www.batcon.org/home/index.asp?idPage=122). +== Changelog == + += trunk = +* Add REQUEST_METHOD to the cache keys. Prevents GET requests receiving bodyless HEAD responses. This change invalidates the entire cache at upgrade time. + += 1.1 = +* Many bugfixes and updates from trunk