Apache, MultiViews and PHP
Prerequisites
- Apache 2.x web server
- Recent (4.3.x, 5.x) PHP version, compiled and installed as a module, not as a CGI binary
- Apache MultiViews enabled for content negotiation
- URLs in documents without file extensions, like the URL of this page
Symptoms
You may have noticed one of the following:
- Search engines (particularly Google) failing to index your pages
406 Not Acceptableerrors in your logs or statistics- User complaints of
406errors from obscure browsers
The problem
The installation documents for PHP currently advise you to add the following line to Apache’s httpd.conf file:
AddType application/x-httpd-php .php .phtml
This tells the server to treat .php and .phtml files as being of media type (aka MIME type) application/x-httpd-php. The PHP module, loaded in a LoadModule directive earlier in the file, tells the server that it handles files of this type when it loads up.
Without MultiViews, this works fine. A request for foo.php will cause the server to pass that file to the PHP module, which will do its stuff—usually serving content as type text/html.
MultiViews will take a request for foo, and find matching files. For example, I could have an HTML file foo.html and a PDF file foo.pdf. MultiViews would determine which of the possible choices best matched the Accept header in the request, and serve the appropriate file.
A standard web browser might send an Accept header of text/html,text/plain,application/xhtml+xml. Apache knows that .html files are of type text/html and that .pdf files are of type application/pdf. MultiViews would therefore choose and serve the HTML file to the browser.
However, in the case of foo.php, the AddType line identifies .php files as type application/x-httpd-php. This does not match any of the types in the above Accept header, so Apache returns 406 Not Acceptable. MultiViews has no way of knowing that your .php file will end up being served as text/html.
The fix!
The workaround is to replace the AddType line with:
AddHandler php-script php AddType text/html php
for PHP 4.x, and:
AddHandler php5-script php AddType text/html php
for PHP-5.x. This works for me with Apache 2.0.49 and PHP 4.3.[56] and 5.0.[01]. If you want to define additional extensions and associated default types, just add additional AddHandler and AddType lines.
Caveat
PHP script output type
MultiViews will use the default type as listed in the AddType line, regardless of what the PHP script ends up outputting.
For example, pages on this site used to check for application/xhtml+xml in the Accept header your browser sends. If found, the page was sent as that type by modifying the Content-Type header, and the DOCTYPE was set to XHTML 1.1. [more information]
With the above simple fix, if a browser submitted a request with an Accept header that includes application/xhtml+xml but did not include text/html, Apache would return a 406 error because MultiViews has been told that .php files are text/html.
I solve this by defining additional extensions:
AddHandler php-script phtml pxhtml AddType text/html phtml AddType application/xhtml+xml pxhtml
(or php5-script as appropriate)
I then write all my files as .phtml files and create symbolic links (Windows users: think shortcuts) pointing to them with the same file name but with a .pxhtml extension:
(file) multiviews.phtml (link) multiviews.pxhtml -> multiviews.phtml
MultiViews then has a choice of document types to serve, and I only need to maintain one file. This also removes the need for me to parse the Accept header: I can use PHP’s $_SERVER['SCRIPT_FILENAME'] to find which of the two files MultiViews chose.
One important consideration here is that Internet Explorer (under certain conditions) may send an Accept header of */*, even though it cannot accept application/xhtml+xml.
Fortunately, when faced with a totally ambiguous choice, Apache’s selection algorithm uses the alphabetically-first filename that satisfies the conditions. By choosing file extensions carefully, you can specify the default type to be used. In my implementation, .phtml comes first, so the text/html variant is the fallback default.
Version dependence
PHP and Apache are being continually updated, and this may not work with all versions.
I don’t believe the solution presented above works with Apache 1.3, due to the 1.3-compatible module not catching php-script. Leo Bouchet wrote to me with a solution which I haven’t verified: read his mail; and there’s another approach at Turnip’s Patch. One possible alternative solution is to use type maps: see the content negotiation and mod_negotiation documents at the Apache web site.
Images
If you leave the extensions off your images, you will still get 406 errors from certain requests.
For example, the Google crawler may follow a link to an image water-bear.jpg referenced only as water-bear. The crawler sends an Accept header that does not include any image types, so MultiViews can’t find an alternative. [more on water bears]
You could provide alternative text or HTML files for each of your images. My policy is not to worry about it.
Acknowledgements
My thanks to Alan Flavell for his help, patience and language negotiation notes page; and to Joshua Slive for providing basically the same information as Alan on Apache’s BugZilla database.