PHP to handle HTTP Status Codes for ErrorDocument
The php part of this article is based on my Advanced WordPress 404.php article from 2008. Many of the following ideas came out of the research performed to enumerate every single Apache ErrorDocument, including learning how to view the defaults and many cool tricks for htaccess.
The PHP HTTP ErrorDocument Handler
Just save this as /err.php
or whatever. The best is to put it in a cgi-bin script-alias directory under your DOCUMENT_ROOT like /cgi-bin/e.php
but most people don't know how. That way you can setup some advanced stuff in a /cgi-bin/.htaccess
file. If you are interested in locking it down, I recommend reading Securing php.ini and php-cgi with .htaccess.
Advantages and Reasons for Using
Fast, HTTP Protocol Compliance, protection. If you are reading this article, you already know and just want to check out the code!
'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Reserved', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 426 => 'Upgrade Required', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 510 => 'Not Extended' ); $status_msg = array( 400 => "Your browser sent a request that this server could not understand.", 401 => "This server could not verify that you are authorized to access the document requested.", 402 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.', 403 => "You don't have permission to access %U% on this server.", 404 => "We couldn't find that uri on our server, though it's most certainly not your fault.", 405 => "The requested method is not allowed for the URL %U%.", 406 => "An appropriate representation of the requested resource %U% could not be found on this server.", 407 => "An appropriate representation of the requested resource %U% could not be found on this server.", 408 => "Server timeout waiting for the HTTP request from the client.", 409 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.', 410 => "The requested resource %U% is no longer available on this server and there is no forwarding address. Please remove all references to this resource.", 411 => "A request of the requested method GET requires a valid Content-length.", 412 => "The precondition on the request for the URL %U% evaluated to false.", 413 => "The requested resource %U% does not allow request data with GET requests, or the amount of data provided in the request exceeds the capacity limit.", 414 => "The requested URL's length exceeds the capacity limit for this server.", 415 => "The supplied request data is not in a format acceptable for processing by this resource.", 416 => 'Requested Range Not Satisfiable', 417 => "The expectation given in the Expect request-header field could not be met by this server. The client sentExpect:
", 422 => "The server understands the media type of the request entity, but was unable to process the contained instructions.", 423 => "The requested resource is currently locked. The lock must be released or proper identification given before the method can be applied.", 424 => "The method could not be performed on the resource because the requested action depended on another action and that other action failed.", 425 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.', 426 => "The requested resource can only be retrieved using SSL. Either upgrade your client, or try requesting the page using https://", 500 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.', 501 => "This type of request method to %U% is not supported.", 502 => "The proxy server received an invalid response from an upstream server.", 503 => "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.", 504 => "The proxy server did not receive a timely response from the upstream server.", 505 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.', 506 => "A variant for the requested resource%U%
is itself a negotiable resource. This indicates a configuration error.", 507 => "The method could not be performed. There is insufficient free space left in your storage allocation.", 510 => "A mandatory extension policy in the request is not accepted by the server for this resource." ); // Get the Status Code if (isset($_SERVER['REDIRECT_STATUS']) && ($_SERVER['REDIRECT_STATUS'] != 200))$sc = $_SERVER['REDIRECT_STATUS']; elseif (isset($_SERVER['REDIRECT_REDIRECT_STATUS']) && ($_SERVER['REDIRECT_REDIRECT_STATUS'] != 200)) $sc = $_SERVER['REDIRECT_REDIRECT_STATUS']; $sc = (!isset($_GET['error']) ? 404 : $_GET['error']); $sc=abs(intval($sc)); // Redirect to server home if called directly or if status is under 400 if( ( (isset($_SERVER['REDIRECT_STATUS']) && $_SERVER['REDIRECT_STATUS'] == 200) && (floor($sc / 100) == 3) ) || (!isset($_GET['error']) && $_SERVER['REDIRECT_STATUS'] == 200) ) { @header("Location: http://{$_SERVER['SERVER_NAME']}",1,302); die(); } // Check range of code or issue 500 if (($sc < 200) || ($sc > 599)) $sc = 500; // Check for valid protocols or else issue 505 if (!in_array($_SERVER["SERVER_PROTOCOL"], array('HTTP/1.0','HTTP/1.1','HTTP/0.9'))) $sc = 505; // Get the status reason $reason = (isset($status_reason[$sc]) ? $status_reason[$sc] : ''); // Get the status message $msg = (isset($status_msg[$sc]) ? str_replace('%U%', htmlspecialchars(strip_tags(stripslashes($_SERVER['REQUEST_URI']))), $status_msg[$sc]) : 'Error'); // issue optimized headers (optimized for your server) @header("{$_SERVER['SERVER_PROTOCOL']} {$sc} {$reason}", 1, $sc); if( @php_sapi_name() != 'cgi-fcgi' ) @header("Status: {$sc} {$reason}", 1, $sc); // A very small footprint for certain types of 4xx class errors and all 5xx class errors if (in_array($sc, array(400, 403, 405)) || (floor($sc / 100) == 5)) { @header("Connection: close", 1); if ($sc == 405) @header('Allow: GET,HEAD,POST,OPTIONS', 1, 405); } echo "n"; echo "n{$sc} {$reason} n{$reason}n{$msg}
n"; } function askapache_global_debug() { # http://www.php.net/manual/en/function.array-walk.php#100681 global $_GET,$_POST,$_ENV,$_SERVER; $g=array('_ENV','_SERVER','_GET','_POST'); array_walk_recursive($g, create_function('$n','global $$n;if( !!$$n&&ob_start()&&(print "[ $"."$n ]n")&&array_walk($$n, create_function('$v,$k', 'echo "[$k] => $vn";'))) echo "<"."p"."r"."e>".htmlspecialchars(ob_get_clean())."<"."/"."pr"."e>";') ); } print_error_page(); //if($_SERVER['REMOTE_ADDR']=='youripaddress')askapache_global_debug(); echo "n"; echo ob_get_clean(); exit; ?>
n
Note: If you are installing this on a non-linux/non-apache machine/server, you will need to read your products documentation for custom error documents. It will work on any machine that can run php.
Htaccess ErrorDocument Tips
The thing is, how do you setup your website to use this php file to be able to handle all those HTTP Status Codes gracefully? You just need to configure your server to use that php file for any Status Codes you want. If you are building an ErrorDocument handling system for a server-wide, multi-site, setup, you will want to instead use the method I use. Instead of using a separate language like PHP, Python, Ruby, Perl, etc, to handle errors, I rely on the very safe and fast SSI method. I detailed the advanced ErrorDocument SSI (.htaccess
or httpd.conf)..
If you instead like most of us, you will be setting this up for 1 site, or 1 DOCUMENT_ROOT serving virtual hosts. For that the best method is to modify your .htaccess file.
Using Redirect in .htaccess to trigger Errors
This is one of my all time favorite discoveries from my apache studies. It's documented elsewhere on this site, but if you want to test it out, just request www.askapache.com/show-error-402
. Of course the error handling that I have in place is quite nice.
Redirect 400 /show-error-400 Redirect 401 /show-error-401 Redirect 402 /show-error-402 Redirect 403 /show-error-403 Redirect 405 /show-error-405 Redirect 406 /show-error-406 Redirect 407 /show-error-407 Redirect 408 /show-error-408 Redirect 409 /show-error-409 Redirect 410 /show-error-410 Redirect 411 /show-error-411 Redirect 412 /show-error-412 Redirect 413 /show-error-413 Redirect 414 /show-error-414 Redirect 415 /show-error-415 Redirect 416 /show-error-416 Redirect 417 /show-error-417 Redirect 418 /show-error-418 Redirect 419 /show-error-419 Redirect 420 /show-error-420 Redirect 421 /show-error-421 Redirect 422 /show-error-422 Redirect 423 /show-error-423 Redirect 424 /show-error-424 Redirect 425 /show-error-425 Redirect 426 /show-error-426 Redirect 500 /show-error-500 Redirect 501 /show-error-501 Redirect 502 /show-error-502 Redirect 503 /show-error-503 Redirect 504 /show-error-504 Redirect 505 /show-error-505 Redirect 506 /show-error-506 Redirect 507 /show-error-507 Redirect 508 /show-error-508 Redirect 509 /show-error-509 Redirect 510 /show-error-510
Powerful Mod_Rewrite Trick
Here's how to combine the power of mod_rewrites ability to parse requests and environment variables with the above Redirect trick to trigger a specific ErrorDocument based on the query_string parameter error. This trick is only on AskApache.com, very powerful trick if you need to force ErrorDocuments.
RewriteCond %{QUERY_STRING} error=([4|5][0-9][0-9]) [NC] RewriteCond %{QUERY_STRING} !404 RewriteRule . /show-error-%1 [L]
ErrorDocument Example for .htaccess
So if you save the php file as err.php
in your DOCUMENT_ROOT, these are the htaccess commands that will enable its use.
The addition of the ?error=num
should be unneccessary on a good linux machine, it's a way for lesser OS's and webhosts to still be able to use errordocuments. Basically Apache handles ErrorDocuments by setting special DEBUGGING variables (Start with REDIRECT_
) so it's very easy to determine the STATUS CODE by just viewing $_SERVER['REDIRECT_STATUS']
. If a recursive type of redirect is going on, you may see $_SERVER['REDIRECT_REDIRECT_STATUS']
. Dumb (consistently) OS's like a Windows server almost always have problems with things like that, because they don't give a hoot about POSIX or standards, why should they when no one can view their code anyway. Here are some more details on the REDIRECT_STATUS
and other ways to use these variables.
If you want to learn how to enumerate and view the different variables that are in your Apache environment, I think I have the best tutorial on the planet for how to do this with PHP and mod_rewrite with mod_headers. That article is the basis for anyone who is hired to do mod_rewrites on a new server without root access. I would say that one article will inform you more about mod_rewrite then any other article on this site.
### # ErrorDocument: In the event of a problem or error, what the server will return to the client. URLs # can begin with a / for local web-paths (relative to DocumentRoot), or be a full URL which the client # can resolve. Alternatively, a message can be displayed. If a malformed request is detected, normal # request processing will be immediately halted and the internal error message returned. # # Prior to version 2.0, messages were indicated by prefixing them with a # single unmatched double quote character. # # The special value default can be used to specify Apache's simple hardcoded message and # will restore Apache's simple hardcoded message. # ErrorDocument 400 /err.php?error=400 ErrorDocument 401 /err.php?error=401 ErrorDocument 402 /err.php?error=402 ErrorDocument 403 /err.php?error=403 ErrorDocument 404 /err.php?error=404 ErrorDocument 405 /err.php?error=405 ErrorDocument 406 /err.php?error=406 ErrorDocument 407 /err.php?error=407 ErrorDocument 408 /err.php?error=408 ErrorDocument 409 /err.php?error=409 ErrorDocument 410 /err.php?error=410 ErrorDocument 411 /err.php?error=411 ErrorDocument 412 /err.php?error=412 ErrorDocument 413 /err.php?error=413 ErrorDocument 414 /err.php?error=414 ErrorDocument 415 /err.php?error=415 ErrorDocument 416 /err.php?error=416 ErrorDocument 417 /err.php?error=417 ErrorDocument 422 /err.php?error=422 ErrorDocument 423 /err.php?error=423 ErrorDocument 424 /err.php?error=424 ErrorDocument 426 /err.php?error=426 ErrorDocument 500 /err.php?error=500 ErrorDocument 501 /err.php?error=501 ErrorDocument 502 /err.php?error=502 ErrorDocument 503 /err.php?error=503 ErrorDocument 504 /err.php?error=504 ErrorDocument 505 /err.php?error=505 ErrorDocument 506 /err.php?error=506 ErrorDocument 507 /err.php?error=507 ErrorDocument 510 /err.php?error=510
« AskApache Password Protection 4.7 Update in 2 WeeksHTTP Status Codes and Htaccess ErrorDocuments »
Comments