Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 104 additions & 12 deletions advanced-cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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 <!-- comment -->
Expand All @@ -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 )
Expand Down Expand Up @@ -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
Expand All @@ -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] );
}
Expand All @@ -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 ) {
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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 </head>
Expand Down Expand Up @@ -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'));

Expand Down
2 changes: 1 addition & 1 deletion batcache.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 19 additions & 16 deletions readme.txt
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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 `</head>` 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 `</head>` 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 ==

Expand All @@ -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