Forum Moderators: phranque

Message Too Old, No Replies

Extensionless URLs, and removing trailing slashes

Got this working, but can't remove trailing slashes?

         

whitenoise

2:44 pm on Jun 22, 2008 (gmt 0)

10+ Year Member Top Contributors Of The Month



Hi all,

I've been looking to remove extensions from the pages on my site, and thanks to the other threads in the forum I've managed to do that.

The one thing I can't figure out is how to remove trailing slashes. For example, the below works:

www.example.co.uk/pagename

This rightly goes to www.example.co.uk/pagename.php. Same with this:

www.example.co.uk/directory/pagename

Goes to www.example.co.uk/directory/pagename.php. However quite often people type in:

www.example.co.uk/pagename/

This just brings an error, because it is clearly looking for a directory with that name that does not exist. How can I simply remove the trailing slash in the above example so it automatically redirects to the same page in the first example?

Here is the code I have so far (courtesy of forum members, most specifically JDmorgan - thanks) . For reference the development/ folder is where I am testing a new website before releasing it live.

RewriteEngine on
#
## Internally rewrite extensionless file requests to .php files ##
#
# If the requested URI does not contain a period in the final path-part
RewriteCond %{REQUEST_URI} !(\.[^./]+)$
# and if it does not exist as a directory
RewriteCond %{REQUEST_FILENAME} !-d
# and if it does not exist as a file
RewriteCond %{REQUEST_FILENAME} !-f
# then add .php to get the actual filename
RewriteRule (.*) /development/$1.php [L]
#
#
## Externally redirect clients directly requesting .php page URIs to extensionless URIs
#
# If client request header contains html file extension
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /([^.]+)\.php\ HTTP
# externally redirect to extensionless URI
RewriteRule ^([^.]+)\.php$ http://www.example.co.uk/development/$1 [R=301,L]

I've tried adding:

# If requested URL does not resolve to an existing directory
RewriteCond %{REQUEST_FILENAME} !-d
# Externally redirect to remove trailing slash
RewriteRule ^(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]

But this doesn't appear to work. Maybe I have put this in the wrong place?

Thanks to anyone who can help.

[edited by: jdMorgan at 3:51 pm (utc) on June 22, 2008]
[edit reason] example.co.uk [/edit]

jdMorgan

3:59 pm on Jun 22, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Your code looks OK, as long as the rules are in the right order. Put your external redirects first, in most-specific to least-specific order, followed by internal rewrites, again in most-specific to least-specific order.

/development/.htaccess:


RewriteEngine on
#
## Redirect to remove trailing slashes on extensionless URL requests
# If requested URL ending with slash does not resolve to an existing directory
RewriteCond %{REQUEST_FILENAME} !-d
# Externally redirect to remove trailing slash
RewriteRule ^(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]
#
## Externally redirect clients directly requesting .php page URIs to extensionless URIs
# If client request header contains [b]php[/b] file extension
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ [b]/[^.]+[/b]\.php\ HTTP
# externally redirect to extensionless URI
RewriteRule ^([^.]+)\.php$ http://www.example.co.uk/development/$1 [R=301,L]
#
## Internally rewrite extensionless file requests to .php files ##
# If the requested URI does not contain a period in the final path-part
RewriteCond %{REQUEST_URI} !(\.[^./]+)$
# and if it does not exist as a directory
RewriteCond %{REQUEST_FILENAME} !-d
# and if it does not exist as a file
RewriteCond %{REQUEST_FILENAME} !-f
# then add .php to get the actual filename
RewriteRule [b](.+)[/b] /development/$1.php [L]

Be sure to completely flush your browser cache before testing any changes to your .htaccess or config files, or any scripts that might generate redirects.

Jim

[edited by: jdMorgan at 4:00 pm (utc) on June 22, 2008]

pageoneresults

4:02 pm on Jun 22, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Be sure to completely flush your browser cache before testing any changes to your .htaccess or config files, or any scripts that might generate redirects.

I'm sure many do this by habit. I still, to this day, forget to do just that when testing. I'll send a notification off to the programming team only to "n/m" it a minute or two later after realizing I failed to do the above. :(

jdMorgan

4:18 pm on Jun 22, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



If you have a test machine or an account dedicated to testing, you can disable the browser cache completely to avoid this problem. However, you then need to remember to always use that account for testing, so this only really helps in formal testing environments. :o

Jim

pageoneresults

4:41 pm on Jun 22, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



jd, if everyone around here had someone like you on staff, this forum would be a ghost town!

whitenoise

1:21 pm on Jun 23, 2008 (gmt 0)

10+ Year Member Top Contributors Of The Month



Thanks JD and pageone for your quick responses! I've re-ordered the code as JD mentioned but this appears to cause more problems!

At the moment everything is being redirected to:

www.example.co.uk/development/whatyoutyped/

This directory does not exist. If doesn't matter if you enter the address without the /, or if you remove the slash and add .php. All requests get the trailing slash added and thus give an error.

I've tried a couple of ideas but still get the same result?

whitenoise

4:05 pm on Jun 24, 2008 (gmt 0)

10+ Year Member Top Contributors Of The Month



Ok, the code does indeed work fine (thanks) however I have discovered another problem.

If I type (for example):

www.example.co.uk/tree/

And the directory tree does not exist, then it will remove the trailing slash and display tree.php which is in the root - which is correct.

However I do want a directory called tree. In this directory I might want files called oak.php, beech.php (you get the idea). So in my site I might create a link to www.example.co.uk/tree/beech. Now I would expect this to display the beech.php file in the directory tree.

The problem comes from if someone links to the below when this directory does exist:

www.example.co.uk/tree/

I would like them to go to tree.php in the root. However since I have htaccess set to not allow directory listings, I get a forbidden message.

How can I get this to work?

OT (a bit): I'm finding these articles useful if anyone wants a good reference [forum.modrewrite.com...]

Many thanks :)

jdMorgan

6:18 pm on Jun 24, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



You are in an unwinnable fight with the HTTP standards here.

www.example.com/tree/ is a request for the directory index page in /tree
www.example.com/tree is a request for the page "tree" in the root directory.

Trying to fight against these conventions will cause you much pain and trouble with search engines and users.

If you want to go against convention, then Apache server is open-source, so I suppose you could modify it... :)

Jim

whitenoise

1:40 pm on Jun 25, 2008 (gmt 0)

10+ Year Member Top Contributors Of The Month



Thanks for your continued help Jd. Maybe I have this all round the wrong way?!

Lets say I have lots of pages in the root that list items to do with a specific category. On the blue widgets page for example, it contains lots of links to do with widgets that are blue. An example link might take a visitor to www.example.co.uk/blue-widgets/widget1.php. The red widgets page would have links to www.example.co.uk/red-widgets/widget1.php etc etc.

The problem here is that the file in the root (blue-widgets.php) is called the same as the folder which contains all its supporting files. This doesn't seem to work with the htaccess I am using.

From what you have said, should I take the file blue-widgets.php and place it in the blue-widgets directory? That way if someone visits www.example.co.uk/blue-widgets/ they see the blue-widgets.php page?

Sorry if I haven't explained myself properly, but as much as I try to understand .htaccess it blows my mind!

jdMorgan

3:55 pm on Jun 25, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



It doesn't matter where in the fielsystem blue-widgets.php is located; URLs and filepaths are two different things, and this becomes more obvious when you use mod_rewrite. The URL published on your Web pages is the "real URL" and the location of the associated resource (file or script) in your server is the "real filepath." You can use mod_rewrite to "associate" any server filepath with any URL you like, as long as both are valid in the context where they are used (i.e. the URL must be valid under HTTP, and the filepath must be valid for the operating system on your server).

It also does not matter that the script name and the directory name are similar -- The trailing slash determines which is being referred to. /blue-widgets can refer to the script (in your root) and the supporting files for blue-wigets can be located in the subdirectory /blue-widgets/.

The method that comes to mind is that if the URL is exactly "/blue-widgets/", then that is an "incorrect" request for the script: Externally redirect to "/blue-widgets" (no trailing slash). If the URL is exactly "/blue-widgets", then that is a request for the script, and all you need to do is to internally rewrite it to "/blue-widgets.php". If the URL is "/blue-widgets/<something>", then that is a request for a supporting file, and, can likely be left alone.

Much depends on what you want to do. I think I understand what you're wanting to do here, but I can't be sure -- I look at an awful lot of different requirements every day... ;)

The critical thing is to come up with a URL-plan and a filesystem plan (where "plan" means both "a course of action" and "a map" here) that is consistent and complies with the requirements of the context in which it is used (URLs on the Web, filepaths in the server). Only then you can start coding... :)

It also helps very much to remember that URLs and filepaths are completely separate things --different naming conventions for the two different contexts-- and that they need not be directly-related. The fundamental job of a server is to convert URLs to filepaths; If desired, mod_rewrite can be used to intercede and affect how this conversion is done.

Jim

whitenoise

11:36 am on Jun 26, 2008 (gmt 0)

10+ Year Member Top Contributors Of The Month



Thats very helpful JD thanks.

Your method was exactly what I would like to happen so I have been experimenting without success. I'm attempting to do the first part of the method but I keep getting error 500, forbidden, or loops.

blue-widgets is a directory that exists, and in my testing I am typing in http://www.example.co.uk/development/blue-widgets/

RewriteRule ^/(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]
----

RewriteRule ^(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]
---

RedirectMatch 301 ^/(.+)/$ http://www.example.co.uk/development/$1
----

RewriteCond %{REQUEST_URI} ^/blue-widgets
RewriteRule ^/(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]
----

RewriteCond %{REQUEST_URI} ^(.+)/$
RewriteRule ^/(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]

And various combinations but I can't seem to make any headway. I probably missing something really fundamental or maybe not even doing what I have properly! Can anyone advise?

jdMorgan

6:10 pm on Jun 26, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



For use in .htaccess, your second attempt is a correct rule:

RewriteRule ^(.+)/$ http://www.example.co.uk/development/$1 [R=301,L]

But let me ask you, what happens when this rule matches, the client is told to re-request "/<whatever>/" from "/development/<whatever>/", and then issues that request?

The rule will be re-invoked for this new HTTP request, and it will redirect the client to "/development/development/<whatever>/". Then we'll go around again and again, each time adding another "/development" subdirectory level to the requested URL, until the client or the server gives up.

You must explicitly exclude requests starting with "/development/" from being redirected by your rule. I suggest a RewriteCond examining %{REQUEST_URI} and using a negative match on the start-anchored pattern -- Look this stuff up in the Apache mod_rewrite doc if not clear.

Jim

whitenoise

8:54 am on Jun 27, 2008 (gmt 0)

10+ Year Member Top Contributors Of The Month



Thanks for your help, I shall do some further reading.

Cheers

njdove

3:28 am on Jul 17, 2008 (gmt 0)

10+ Year Member



@whitenoise I happened to recently address another issue with trailing spaces, and I'm always down for a RewriteRule puzzle. In my brief testing, the following .htaccess file offered the behavior you describe. ymmv:


RewriteEngine On


# Given the directory structure:
# /tree.php
# /tree/beech.php
# Perform the following redirects:
# http://server/tree -internal-> /tree.php
# http://server/tree/ -redirect-> http://server/tree
# http://server/tree/beech -internal-> /tree/beech.php


# Disable Apache 2.0+ mod_dir feature that redirects URLs for valid
# directories by appending slash.
DirectorySlash Off


# If the file name includes a trailing slash, store it in
# VIRTUAL_FILE, sans slash
RewriteCond %{REQUEST_FILENAME} ^(.+)/$
RewriteRule .* - [E=VIRTUAL_FILE:%1]


# If the URI has a trailing slash and a PHP file of the same name
# exists, redirect.
RewriteCond %{REQUEST_URI} ^/(.+)/$
RewriteCond %{ENV:VIRTUAL_FILE}.php -f [OR]
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule .* - [E=VIRTUAL_URI:%1,C]
RewriteRule ^(.+)/$ http://%{HTTP_HOST}/%{ENV:VIRTUAL_URI} [R=301,L]


# Internal redirect to ".php" if it exists
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule (.+) $1.php [L]