“We need all our URL’s to have a trailing slash”, was a request I had in recently.
This sounds easy enough, a quick rewrite rule will do the trick, and that is what I added.
RewriteRule ^(.*[^/])$ /$1/ [R=301,L]
I look at the start of the URL, grab every character to the end of the URL, making sure the last one isn’t a slash already, then rewrite that with slashes around it, issuing a 301 redirect and informing Apache not to process any more rules.
This works great for pages already on the server, for example /guestbook
becomes /guestbook/
. However, what if we wanted to load /logo.gif
, this would rewrite to /logo.gif/
and cause a error.
OK, so we need not to match every character in the URL, but only if it doesn’t have a dot in there. The site in question was a Zend Framework application, and this caused it’s own problems.
Zend Framework applications have their own rewriting rules…
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
This checks if the requested resource is actually a real object Apache can serve, if not it passes it over to the Zend Framework to handle internally via the index.php script.
So what happens if /guestbook
is actually a Zend Framework application?
Well, the URL would be rewritten to /index.php/
.
Why? Well the first rewrite we added a slash, this caused the page to re-requested, this then passed to the Zend Framework rewrites, this saw it needed to rewrite to index.php, this caused the page to be re-requested again internally, which added a slash to the end, and redirected again. Finally this request was /index.php/ which doesn’t exist and failed.
Wow, that was a mouthful, let’s look at that as a sequence diagram.
What we need is to only rewrite the requested URL if it doesn’t have a trailing slash and if it doesn’t have a dot in the request and if it isn’t an internal Apache redirect. We also only want to rewrite if the request is a GET, POSTs (DELETE’s and PUT’s too) don’t support rewriting in the browser.
This indicates a series a conditions that need to be met, so tells me we should be using Apache’s RewriteCond
directive.
Let’s check for a GET request first…
RewriteCond %{REQUEST_METHOD} GET [NC]
The %{REQUEST_METHOD}
variable in a rewrite rule tells us the HTTP method used to make the request, so we just need to check if that is GET. The [NC]
makes the check case insensitive so matches GET, get, Get, etc…
Next we need to see if the request already has a trailing slash.
RewriteCond %{REQUEST_URI} !/$
This checks the requested URI and sees if the last character isn’t a slash.
Now we need to check if there are any dots in the request.
RewriteCond %{REQUEST_URI} !.
This just checks that there isn’t a dot in the requested URI. As dot normally matches any character, we need to escape this with a clash first.
If all these conditions match, we can issue our RewriteRule.
RewriteRule ^.*$ %{REQUEST_URI}/ [R=301,L]
This takes the requested URI, adds a slash and issues a 301 status code to force the browser to re-request the page and change the URL shown in the address bar. We add the L
to tell Apache not to process any more rules.
Let’s put this all together, and insert it above the normal Zend Framework rewrites, but after RewriteEngine On
.
RewriteCond %{REQUEST_METHOD} GET [NC]
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_URI} !.
RewriteRule ^.*$ %{REQUEST_URI}/ [R=301,L]
Let’s request /guestbook
again, and see what happens.