Archive for July 2, 2008

Wednesday, July 2, 2008

Adobe Reader 9

Gus Mueller makes fun of Adobe Reader 9. Good to know you can get the “recomposition” status down to a tenth of a percent. By the way, there’s still no universal binary.

Update (2008-07-04): Mark Pilgrim comments, and John Gruber writes:

Adobe today reminds me a bit of Apple in the mid-’90s. Tremendous engineering and design talent in the company. A loyal base of users built over 20 years. But management that just doesn’t get it at all, and seems hell-bent on running the company into the ground.

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.