Unexpected Apache Upgrade (4th April 2012)

The data centre which supplies my host, D&S Hosting, decided to upgrade our Apache 1.3 to a newer version without telling us in advance. This website was a 500 Internal Server error for several hours and Site Surgeon was not processing its PHP.

Adjusting the AddHandler in the .htaccess got PHP working in both places. But the .htaccess for Project Cerbera also had a couple of syntax issues and a couple of deprecated features. Which I’ve had to re-code on the spot.

Good thing I wasn’t on holiday! Broken websites aren’t the best selling tool for a website developer.

Fixing AddHandler to run PHP in .html Files

Before, I used a list of file extensions:

AddHandler application/x-httpd-php .html .htm .php

Now I use one per line:

# Process <?php ... ?> as PHP in HTML but not <? ... ?>:
AddHandler application/x-httpd-php .php
AddHandler application/x-httpd-php .html
AddHandler application/x-httpd-php .htm
AddHandler application/x-httpd-php-source .phps

Easy enough, after deducing it from recent samples online. Didn’t notice people saying the list approach had become obsolete.

.htaccess Syntax Gremlins

Had the idea to validate my .htaccess file and immediately found the easy-as-pie .htaccess checker by Lynxx. A quick Copy and Paste into its text box followed by a click on check it » and I had a neat report.

Below is a syntax-highlit version of the errors it found.

Lyxx found 4 problems in the code segment(s) below.

line 15: (3)
php_value short_open_tag Off
line 37: (10)
# http://www.accessifyforum.com/viewtopic.php?p=49758#49758
line 284: (12)
Redirect permanent /gta/3/tutorials/handling/definition.cfg  http://projectcerbera.com/gta/3/tutorials/handling
line 405: (201)
RedirectMatch gone ^/web/study/2007/tables/clark2006\guis\.*$

No idea what the numbers in brackets mean, possible the internal error codes it uses? Anyway, here are the fixes by line number:

  1. Short tags are obsolete in new PHP so this line isn’t needed.
  2. Wasn’t actually. The 1st # character comments out the 2nd # character.
  3. Two spaces instead of one. (Ouch, new Apache is strict!)
  4. Should be a forward slash before guis as it’s part of a URL. (Using \g in a RegEx isn’t valid because there is no special character abbreviated to \g.)

Rewriting my RewriteRules

Zcorpan figured out extensionless URLs for me many moons ago. It wasn’t quite working in some situations now, though.

Specifically, requesting a .html file which no longer existed would simply produce the 404 Not Found error. What should happen is:

  1. The .html gets stripped anyway,
  2. My forest of Redirect statements eventually matches the broken URL;
  3. You are taken to the new URL.

So I searched for a while and tried to untangle the samples which would do what I want. Eventally pieced together the solutions from jdMorgan’s RewriteRule samples on Webmaster World to get what I needed. This isn’t safe for query strings; since I don’t use any I have left them out.

# Canonical clean URLs
# Query string support by zcorpan: http://www.accessifyforum.com/viewtopic.php?t=7443#49789
Options +MultiViews
# They got broken on 2012-04-05 after an unexpected major Apache server upgrade by data centre.
# Combined with code by jdMorgan: http://www.webmasterworld.com/apache/3480555.htm
RewriteEngine ON
RewriteBase /
# If direct client request for .html files
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /[^.]+\.html\ HTTP/
# Redirect to URL without extension
RewriteCond %{THE_REQUEST} !\?.*\.html
RewriteRule ^([^.]+)\.html$ $1 [R=301,L]
# Redirect index files to their directory name, so "foo/index" becomes "foo/"
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /([^/]+/)*index\ HTTP/
RewriteCond %{THE_REQUEST} !\?.*\index
RewriteRule ^(([^/]+/)*)index\.html$ $1 [R=301,L]

Coolest thing is that now, I don’t need the domain name on the ‘corrected’ side of my rewrites or redirects! Awesome! I can finally run the exact same .htaccess locally and remotely.

Newer and better, in this case.

And Again…

We changed to a new host, which provides a much more beefy server. But PHP was broken again. Why?!

The fix seemed so simple at first, after immediately discovering a value has changed in PHP5:

AddHandler application/x-httpd-php-5 .php .html .htm
AddHandler application/x-httpd-php-source .phps

The final line makes PHP5 syntax highlight .phps files automatically. (Same as previous code.)

That got PHP working but it didn’t have access to a function my code uses:

Fatal error: Call to undefined function apache_lookup_uri() in [secret/folder/location] on line 8.

So I started cooking lunch, with my site still broken. After I returned (and a few more Google searches later) I found the solution. Here’s how I used to calculate the Last-Modified HTTP response header:

// Get information about requested page:
$file_info = apache_lookup_uri($_SERVER['SCRIPT_NAME']);
$filespec = $file_info->filename; // complete path after any MultiView operations
$last_modified = gmdate(DATE_RFC2822, filemtime($filespec)); // RFC 2822

Here’s how I do it now:

// Updated for SuPHP: http://uk3.php.net/manual/en/function.getlastmod.php#46613
$filespec = $_SERVER['SCRIPT_FILENAME'];
$last_modified = gmdate(DATE_RFC2822, filemtime($filespec)); // RFC 2822
$modified_date = gmdate('l j', filemtime($filespec))
                 . '<sup>' . gmdate('S', filemtime($filespec)) . '</sup> '
                 . gmdate('F Y', filemtime($filespec))
                 . ' at ' . gmdate('g:ia', filemtime($filespec))
                 . ' <acronym title="Universal Coordinated Time">UTC</acronym>';

So the live site now works. But now the local site wasn’t processing PHP! For some bizarre reason, it only works with the old AddHandler value. So, to keep parity between my local .htaccess and the live one, I added a conditional directive:

# Local WAMP server doesn't recognise: application.x-httpd-php5
# Requires enabling "mod_version.c" which WAMP calls "version module".
<IfModule mod_version.c>
 <IfVersion = 2.2.21>
  AddHandler application/x-httpd-php .php .html .htm

Since the live version is already more recent and the local will only change if I choose to change it, this should be an adequate check. Still bizarre, though.