WP Super Cache
I recently installed WP Super Cache to speed up my WordPress blogs. The regular WP-Cache plug-in saves processed copies of pages, reducing the database and PHP processing overhead, but the pages are still served via PHP. WP Super Cache takes this to the next level, using mod_rewrite to send requests directly to cached HTML files:
RewriteCond %{REQUEST_METHOD} !=POST
RewriteCond %{QUERY_STRING} !.*s=.*
RewriteCond %{QUERY_STRING} !.*attachment_id=.*
RewriteCond %{HTTP_COOKIE} !^.*(comment_author_|wordpress|wp-postpass_).*$
RewriteCond %{DOCUMENT_ROOT}/blog/wp-content/cache/supercache/%{HTTP_HOST}/blog/$1/index.html -f
RewriteRule ^(.*) /blog/wp-content/cache/supercache/%{HTTP_HOST}/blog/$1/index.html [L]
If a page isn’t yet cached, the -f test will fail, and the request will be directed to the regular WordPress PHP script (using WP-Cache). It’s a nice system, but I hit a few snags setting it up, so I wanted to document them here.
The first problem was that the super cache was not being generated. Every request was being handled by WP-Cache. It turns out that WP Super Cache (sensibly) doesn’t add a page to the super cache unless $_GET is empty. I was using a .htaccess file from a much older version of WordPress, and it was packing the query string with elements of the path:
RewriteRule ^archives/?$ /blog/index.php?pagename=archives [QSA]
RewriteRule ^category/(.*)/(feed|rdf|rss|rss2|atom)/?$ /blog/wp-feed.php?category_name=$1&feed=$2 [QSA]
RewriteRule ^category/?(.*) /blog/index.php?category_name=$1 [QSA]
RewriteRule ^author/(.*)/(feed|rdf|rss|rss2|atom)/?$ /blog/wp-feed.php?author_name=$1&feed=$2 [QSA]
RewriteRule ^author/?(.*) /blog/index.php?author_name=$1 [QSA]
RewriteRule ^([0-9]{4})/?([0-9]{1,2})?/?([0-9]{1,2})?/?([_0-9a-z-]+)?/?([0-9]+)?/?$ /blog/index.php?year=$1&monthnum=$2&day=$3&name=$4&page=$5 [QSA]
RewriteRule ^([0-9]{4})/?([0-9]{1,2})/([0-9]{1,2})/([_0-9a-z-]+)/(feed|rdf|rss|rss2|atom)/?$ /blog/wp-feed.php?year=$1&monthnum=$2&day=$3&name=$4&feed=$5 [QSA]
RewriteRule ^([0-9]{4})/?([0-9]{1,2})/([0-9]{1,2})/([_0-9a-z-]+)/trackback/?$ /blog/wp-trackback.php?year=$1&monthnum=$2&day=$3&name=$4 [QSA]
RewriteRule ^feed/?([_0-9a-z-]+)?/?$ /blog/wp-feed.php?feed=$1 [QSA]
RewriteRule ^comments/feed/?([_0-9a-z-]+)?/?$ /blog/wp-feed.php?feed=$1&withcomments=1 [QSA]
Updating .htaccess file to let WordPress itself parse the path:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /blog/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
</IfModule>
# END WordPress
fixed that problem.
Now the posts were being saved to the super cache, and served from it, but the feeds were only using the regular cache. WP Super Cache is hard-coded not to cache feeds in order to avoid serving them with an incorrect content type (a.k.a. media type or MIME type). Feeds are supposed to be served as application/rss+xml or application/atom+xml, and Feed Validator will complain if they aren’t. But the super-cached files are all stored as index.html so Apache will serve them as text/html. (This isn’t a problem when using WP-Cache, because it sends the proper HTTP header depending on the type of feed being generated.)
To fix this, I modified WP Super Cache to generate local .htaccess files inside the super cache:
if (is_feed()) {
$type = get_query_var('feed');
$type = str_replace('/','',$type);
switch ($type) {
case 'atom':
$mediaType = "application/atom+xml";
break;
case 'rdf':
$mediaType = "application/rdf+xml";
break;
case 'rss':
case 'rss2':
default:
$mediaType = "application/rss+xml";
}
$htaccess = @fopen ("{$dir}.htaccess", 'w');
if ($htaccess) {
fputs($htaccess, "AddType $mediaType .html");
fclose($htaccess);
}
}
For example, the file blog/wp-content/cache/supercache/mjtsai.com/blog/feed/rss2/.htaccess changes the content type for the index.html file:
AddType application/rss+xml .html
Then I modified WP Super Cache’s wp-cache-phase2.php to remove the is_feed() check from the line:
if( !empty( $_GET ) || is_feed() || ( $super_cache_enabled == true && is_dir( substr( $supercachedir, 0, -1 ) . '.disabled' ) ) )
Now the feeds are super cached and served with the correct content types.
2 Comments »
I put it slightly below that, just above “$new_cache = true;”. Your way should work, too, though.
RSS feed for comments on this post. TrackBack URI
Enjoyed the post, but one question:
Where did you put the first modification (the one that generates the .htaccess file for RSS Feeds)? I put it in the function "wp_cache_ob_callback" at line 189 (after the block "if( $user_info == '' || $do_cache === true )") and it seems to be working, but I was wondering where you put it.