Tuesday

Adding A Trailing Slash To A Zend Framework URL


 "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.
sequence diagram showing apache rewrites and Zend Framework working incorrectly
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.
sequence diagram showing apache rewrites and Zend Framework
courtsy:  robertprice dot co dot uk

No comments:

Django URLs

In Django, the urls.py file is where you define the URL patterns for your web application. There are several ways to write the urls.py fil...