Skip to content

Commit

Permalink
Add advanced cache
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoColomb committed Dec 9, 2018
1 parent 4d3331a commit 651e624
Show file tree
Hide file tree
Showing 6 changed files with 623 additions and 25 deletions.
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
# Redis Object Cache for WordPress
# Redis Cache for WordPress

> A persistent object cache backend powered by Redis. Supports [Predis](https://github.com/nrk/predis/), [PhpRedis (PECL)](https://github.com/phpredis/phpredis), replication, clustering and [WP-CLI](http://wp-cli.org/).
> A persistent cache backend powered by Redis.
[![Build Status](https://travis-ci.com/LeoColomb/wp-redis.svg?branch=master)](https://travis-ci.com/LeoColomb/wp-redis)

## Features

* Enable the two cache wrappers for WordPress
* Object cache
* Page cache
* Adds handy [WP-CLI](http://wp-cli.org/) commands
* Supports major PHP Redis drivers
* [Predis](https://github.com/nrk/predis/)
* [PhpRedis (PECL)](https://github.com/phpredis/phpredis)
* Supports replication and clustering


## Installation

Expand All @@ -13,7 +24,8 @@
"extra": {
"dropin-paths": {
"web/app/": [
"package:leocolomb/wp-redis:dropins/object-cache.php"
"package:leocolomb/wp-redis:dropins/object-cache.php",
"package:leocolomb/wp-redis:dropins/page-cache.php"
]
}
}
Expand Down Expand Up @@ -43,7 +55,6 @@ Constant name|Default value|Description
`WP_REDIS_DATABASE`|`0`|Accepts a numeric value that is used to automatically select a logical database with the `SELECT` command.
`WP_REDIS_PASSWORD`|_not set_|Accepts a value used to authenticate with a Redis server protected by password with the `AUTH` command.


### Parameters

Constant name|Default value|Description
Expand All @@ -54,6 +65,21 @@ Constant name|Default value|Description
`WP_REDIS_IGNORED_GROUPS`|`['counts', 'plugins']`|Set the cache groups that should not be cached in Redis.
`WP_REDIS_IGBINARY`|_not set_|Set to `true` to enable the [igbinary](https://github.com/igbinary/igbinary) serializer.

### Page cache

Constant name|Default value|Description
--|--|--
`WP_CACHE`|`false`|Set to `true` to enable advanced page caching. If not set, the Redis page cache will not be used.
`WP_REDIS_TIMES`|`2`|Only cache a page after it is accessed this many times.
`WP_REDIS_SECONDS`|`120`|Only cache a page if it is accessed `$times` in this many seconds. Set to zero to ignore this and use cache immediately.
`WP_REDIS_MAXAGE`|`300`|Expire cache items aged this many seconds. Set to zero to disable cache.
`WP_REDIS_GROUP`|`'redis-cache'`|Name of object cache group used for page cache.
`WP_REDIS_UNIQUE`|`[]`|If you conditionally serve different content, put the variable values here using the `add_variant()` method.
`WP_REDIS_HEADERS`|`[]`|Add headers here as `name => value` or `name => [values]`. These will be sent with every response from the cache.
`WP_REDIS_UNCACHED_HEADERS`|`['transfer-encoding']`|These headers will never be cached. (Use lower case only!)
`WP_REDIS_CACHE_CONTROL`|`true`|Set to `false` to disable `Last-Modified` and `Cache-Control` headers.
`WP_REDIS_USE_STALE`|`true`|Is it ok to return stale cached response when updating the cache?
`WP_REDIS_NOSKIP_COOKIES`|`['wordpress_test_cookie']`|Names of cookies - if they exist and the cache would normally be bypassed, don't bypass it.

## Replication & Clustering

Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "leocolomb/wp-redis",
"description": "A persistent object cache backend for WordPress powered by Redis.",
"type": "wordpress-dropin-bundle",
"description": "A persistent cache backend for WordPress powered by Redis.",
"type": "wordpress-dropins",
"homepage": "https://github.com/LeoColomb/wp-redis",
"license": "GPL-3.0+",
"authors": [
Expand All @@ -18,6 +18,8 @@
"wordpress-dropin",
"wordpress-cache",
"cache-object",
"cache-page",
"cache-advanced",
"redis",
"wordpress"
],
Expand Down
210 changes: 210 additions & 0 deletions dropins/advanced-cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?php

if ((! defined('WP_REDIS_DISABLED') || ! WP_REDIS_DISABLED)
&& class_exists('WP_Redis_Page_Cache')
) :

/**
* Advanced Cache API
*/

$redis_cache = new WP_Redis_Page_Cache();
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_MISS);

// Don't cache interactive scripts or API endpoints
if (in_array(basename($_SERVER['SCRIPT_FILENAME']), [
'wp-cron.php',
'xmlrpc.php',
])) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_BYPASS);

return;
}

// Don't cache javascript generators
if (strpos($_SERVER['SCRIPT_FILENAME'], 'wp-includes/js') !== false) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_BYPASS);

return;
}

// Only cache HEAD and GET requests
if (isset($_SERVER['REQUEST_METHOD']) && ! in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD'])) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_BYPASS);

return;
}

// Don't cache when cookies indicate a cache-exempt visitor
if (is_array($_COOKIE) && ! empty($_COOKIE)) {
foreach (array_keys($_COOKIE) as $cookie) {
if (in_array($cookie, $redis_cache->noskip_cookies)) {
continue;
}

if (strpos($cookie, 'wp') === 0 ||
strpos($cookie, 'wordpress') === 0 ||
strpos($cookie, 'comment_author') === 0
) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_BYPASS);

return;
}
}
}

if (! defined('WP_CONTENT_DIR')) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_DOWN);

return;
}

if (! require_once WP_CONTENT_DIR . '/object-cache.php') {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_DOWN);

return;
}

wp_cache_init();

if (! ($wp_object_cache instanceof WP_Redis_Object_Cache)) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_DOWN);

return;
}

// Cache is disabled
if ($redis_cache->max_age < 1) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_BYPASS);

return;
}

// Necessary to prevent clients using cached version after login cookies set
if (defined('WP_REDIS_VARY_COOKIE') && WP_REDIS_VARY_COOKIE) {
header('Vary: Cookie', false);
}

if (function_exists('wp_cache_add_global_groups')) {
wp_cache_add_global_groups([$redis_cache->group]);
}

$redis_cache->setup_request();
$redis_cache->do_variants();
$redis_cache->generate_keys();

$genlock = false;
$do_cache = false;
$serve_cache = false;
$cache = wp_cache_get($redis_cache->key, $redis_cache->group);

if (isset($cache['version']) && $cache['version'] !== $redis_cache->url_version) {
// Refresh the cache if a newer version is available
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_EXPIRED);
$do_cache = true;
} else if ($redis_cache->seconds < 1 || $redis_cache->times < 2) {
if (is_array($cache) && time() < $cache['time'] + $cache['max_age']) {
$do_cache = false;
$serve_cache = true;
} else if (is_array($cache) && $redis_cache->use_stale) {
$do_cache = true;
$serve_cache = true;
} else {
$do_cache = true;
}
} else if (! is_array($cache) || time() >= $cache['time'] + $redis_cache->max_age - $redis_cache->seconds) {
// No cache item found, or ready to sample traffic again at the end of the cache life

wp_cache_add($redis_cache->req_key, 0, $redis_cache->group);
$requests = wp_cache_incr($redis_cache->req_key, 1, $redis_cache->group);

if ($requests >= $redis_cache->times) {
if (is_array($cache) && time() >= $cache['time'] + $cache['max_age']) {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_EXPIRED);
}

wp_cache_delete($redis_cache->req_key, $redis_cache->group);
$do_cache = true;
} else {
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_IGNORED);
$do_cache = false;
}
}

// Obtain cache generation lock
if ($do_cache) {
$genlock = wp_cache_add("{$redis_cache->url_key}_genlock", 1, $redis_cache->group, 10);
}

if ($serve_cache &&
isset($cache['time'], $cache['max_age']) &&
time() < $cache['time'] + $cache['max_age']
) {
// Respect ETags
$three04 = false;

if (isset($_SERVER['HTTP_IF_NONE_MATCH'], $cache['headers']['ETag'][0]) &&
$_SERVER['HTTP_IF_NONE_MATCH'] == $cache['headers']['ETag'][0]
) {
$three04 = true;
} else if ($redis_cache->cache_control && isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$client_time = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);

if (isset($cache['headers']['Last-Modified'][0])) {
$cache_time = strtotime($cache['headers']['Last-Modified'][0]);
} else {
$cache_time = $cache['time'];
}

if ($client_time >= $cache_time) {
$three04 = true;
}
}

// Use the cache save time for `Last-Modified` so we can issue "304 Not Modified",
// but don't clobber a cached `Last-Modified` header.
if ($redis_cache->cache_control && ! isset($cache['headers']['Last-Modified'][0])) {
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $cache['time']) . ' GMT', true);
header('Cache-Control: max-age=' . ($cache['max_age'] - time() + $cache['time']) . ', must-revalidate', true);
}

$redis_cache->do_headers($redis_cache->headers, $cache['headers']);

if ($three04) {
$protocol = $_SERVER['SERVER_PROTOCOL'];

if (! preg_match('/^HTTP\/[0-9]{1}.[0-9]{1}$/', $protocol)) {
$protocol = 'HTTP/1.0';
}

header("{$protocol} 304 Not Modified", true, 304);
$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_HIT);
exit;
}

if (! empty($cache['status_header'])) {
header($cache['status_header'], true);
}

$redis_cache->cache_status_header($redis_cache::CACHE_STATUS_HIT);

if ($do_cache && function_exists('fastcgi_finish_request')) {
echo $cache['output'];
fastcgi_finish_request();
} else {
echo $cache['output'];
exit;
}
}

if (! $do_cache || ! $genlock) {
return;
}

$wp_filter['status_header'][10]['redis_cache'] = [
'function' => [&$redis_cache, 'status_header'],
'accepted_args' => 2
];

ob_start([&$redis_cache, 'output_callback']);
endif;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

class WP_Redis_Object_Cache_CLI_Commands extends WP_CLI_Command
class WP_Redis_CLI_Commands extends WP_CLI_Command
{

/**
Expand Down
37 changes: 19 additions & 18 deletions src/class-wp-redis-object-cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,24 @@ public function __construct($fail_gracefully = true)
$parameters = [
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379
'port' => 6379,
'client' => 'pecl'
];

foreach (['scheme', 'host', 'port', 'path', 'password', 'database'] as $setting) {
array_map(function ($setting) {
$constant = sprintf('WP_REDIS_%s', strtoupper($setting));
if (defined($constant)) {
$parameters[$setting] = constant($constant);
}
}
}, [
'scheme',
'host',
'port',
'path',
'password',
'database',
'client'
]);

if (defined('WP_REDIS_GLOBAL_GROUPS') && is_array(WP_REDIS_GLOBAL_GROUPS)) {
$this->global_groups = WP_REDIS_GLOBAL_GROUPS;
Expand All @@ -152,16 +161,8 @@ public function __construct($fail_gracefully = true)
$this->ignored_groups = WP_REDIS_IGNORED_GROUPS;
}

$client = defined('WP_REDIS_CLIENT') ? WP_REDIS_CLIENT : null;

if (class_exists('Redis') && strcasecmp('predis', $client) !== 0) {
$client = 'pecl';
} else {
$client = 'predis';
}

try {
if (strcasecmp('pecl', $client) === 0) {
if (class_exists('Redis') && strcasecmp('pecl', $parameters['client']) === 0) {
$this->redis_client = sprintf('PhpRedis (v%s)', phpversion('redis'));

if (defined('WP_REDIS_SHARDS')) {
Expand All @@ -187,12 +188,12 @@ public function __construct($fail_gracefully = true)
}
}

if (strcasecmp('predis', $client) === 0) {
if (strcasecmp('predis', $parameters['client']) === 0) {
$this->redis_client = 'Predis';

// Require PHP 5.6 or greater
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
throw new Exception;
throw new Exception();
}

// Load bundled Predis library
Expand All @@ -205,13 +206,13 @@ public function __construct($fail_gracefully = true)

if (defined('WP_REDIS_SHARDS')) {
$parameters = WP_REDIS_SHARDS;
} elseif (defined('WP_REDIS_SENTINEL')) {
$parameters = WP_REDIS_SERVERS;
$options['replication'] = 'sentinel';
$options['service'] = WP_REDIS_SENTINEL;
} elseif (defined('WP_REDIS_SERVERS')) {
$parameters = WP_REDIS_SERVERS;
$options['replication'] = true;
if (defined('WP_REDIS_SENTINEL')) {
$options['replication'] = 'sentinel';
$options['service'] = WP_REDIS_SENTINEL;
}
} elseif (defined('WP_REDIS_CLUSTER')) {
$parameters = WP_REDIS_CLUSTER;
$options['cluster'] = 'redis';
Expand Down
Loading

0 comments on commit 651e624

Please sign in to comment.