Back to Top

Monday, April 28, 2008

HTTP Redirection with PHP - a complete solution

The problem: you need a generic code which redirects to the current URL or some URL relative to the current one.

One of the reasons you might want to do that is to avoid the possibility of users submitting a POST form multiple times (even though both IE and FF - and I suppose also Opera and Safari) give warnings in such cases, you should avoid depending, where possible, on end users making the right decision.

Also, why generic, why not hardcode it in a configuration file? The first answer would be that you have one less parameter to set when deploying the application (and deployment can mean many things: testing servers, development servers, production servers, or maybe the script is a product used by many people on many different servers). The second part is that the same server can (possibly) be referenced by multiple names:

  • By IP address
  • As localhost, 127.0.0.1, etc, when you are locally on the server
  • By an internal DNS/WINS name (if it is a Windows box for example)
  • By an external DNS name
  • By the IP address of other interfaces (real, or virtual like a VPN)
  • ...

You must replicate in the redirect the exact URL type, otherwise cookies will be invalidated at the very least, or the browser will be completely unable to connect (if you give an interal IP address to an external user for example).

Sidenote: the easy solution would be to use relative URL, like you can in HTML tags. Unfortunately the HTTP/1.1 standard (as well as the 1.0 standard) specifically states that (emphasis added):

The field value consists of a single absolute URI.

Also there is no HTTP header corresponding to the META refresh method, although the attribute name (http-equiv) certainly seems to imply that, and in fact it is the case in other situations (for example in the case of Content-Encoding). Also, using the META solution has many drawbacks:

  • It is deprecated
  • It can only be used for HTML content (not for images, CSS, javascript, etc)
  • It supposes that the receiver is a full browser rather than an automated client like the search engine crawlers, curl, wget, etc

Getting back to the problem, we must determine the following three parts of the URL:

  1. the protocol (HTTP or HTTPS)
  2. the server name/IP (and which was used) together with the port (which may be implicit or explicit)
  3. the query string (all that comes after the server/port)

By the way, I don't claim to have invented something new/unique, the pieces can be found in the documentation and the user comments, I just put it together. Also, take care that this only applies to PHP running under Apache (the most common scenario) and I have no idea if/how this applies to IIS for example (although it should work).

Finally some code:

print "The full URL is: " 
  . (('on' == @$_SERVER['HTTPS']) ? 'https://' : 'http://')
  . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])
  . $_SERVER['REQUEST_URI'];

The part which usually isn't found in the examples is the http/https part. Also note that the HTTP_HOST value will contain the exact method you used to access the site (so it you typed localhost, it will contain localhost, if you typed 127.0.0.5:80, it will contain 127.0.0.5:80 and so on).

The redirection will work like this then:

$target_url = (('on' == @$_SERVER['HTTPS']) ? 'https://' : 'http://')
  . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])
  . $_SERVER['REQUEST_URI'];
header("Location: $target_url");
exit;

A common usecase (for me at least) is to remove the GET parameters before the redirection:

$target_url = (('on' == @$_SERVER['HTTPS']) ? 'https://' : 'http://')
  . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])
  . $_SERVER['REQUEST_URI'];
$target_url = preg_replace('/\\?.*/s', '', $target_url); 
header("Location: $target_url");
exit;

Also, take care that, as the documentation notes, session id's are not automagically included in the URL even if the url rewriting is activated, so you need to include them manually if you use this feature (I won't present code for this though since it can be quite complicated depending on the usecase - do you need to handle existing GET parameters? are those parameters always present? etc, etc)

Update: Added fallback to "SERVER_NAME", to handle situations where the connecting client doesn't send the "Host" header (ie. it isn't HTTP/1.1)

0 comments:

Post a Comment

You can use some HTML tags, such as <b>, <i>, <a>. Comments are moderated, so there will be a delay until the comment appears. However if you comment, I follow.