Davide Salerno
Davide Salerno

Reputation: 1

Zend Framework /index/ redirect throught htaccess to avoid content duplication

I'd like to know if it is possible to add a rule to the htaccess of my ZF app to redirect all the URLs that ends with the segment /index/ (such as http://domain.ext/index/) to the same URL without the /index/ suffix.

I've tried with this simple rule:

RedirectMatch ^(.*)/(index(/)?)$ http://localhost$1

but it doesn't work as expected (with other frameworks such as FuelPHP it works like a charm).

I know that this can be done via PHP using a plugin but I'd like to make the redirect via Apache to improve the performance of the application.

Upvotes: 0

Views: 1446

Answers (4)

vkostromin
vkostromin

Reputation: 14228

RewriteRule ^(.*)/index(?|$)$ $1$2 [R=301,L]

this works with urls

/index => /
/index?page=2 => /?page2
/index/index => /
/index/index?page=2 /?page2

you need remove trailing slash before, for url like /index/, index/index/

RewriteRule ^(.*)/$ /$1 [L,R=301]

url like /index/help will work without changes

Upvotes: 0

Adrian World
Adrian World

Reputation: 3128

I don't know why nobody jumped in here, it is not that complicated?

A config file is executed from top to bottom and certain rules cause an immediate exit. If the rule defines an external redirect the server will perform that redirect immediately and all following rules are therefore ignored. If the redirect is back to the same server and config file then it is just a new game with the rules! If the redirect rule does not apply anymore it is on to the next rule. If the rule would still apply you get a loop.

Similar thing with a RewriteRule that matches and has [L]. L means "Stop the rewriting process here and don't apply any more rewrite rules". This quote is straight from the manual

Now you simply have to define some logic in what order you want to apply certain rules. Your request about the RedirectMatch for any /index/ path is certainly something you want to have very early to the top of the config. If there is a match your config will end here and perform a redirect! The browser will send a new request and we have a new game.

The RewriteRule to an index.php is something we will add very late at the bottom. It may be our last resort like a if all fails then rule. I does not matter if this is the Zend Framework or any other application you funnel through an index.php or other script for that matter.

The following rules should cover any variation with index, including .php, .htm and .html and finally trigger the index.php file for your ZF application.

RedirectMatch ^(/.*)/(index.(php|html|htm)|index)/?$ $1 

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d 
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

When testing redirect rules be careful with your browser and use one where you can totally reset all cache and history settings. All current browsers are notorious in "remembering" redirects. If they learned a redirect rule they will perform that redirect internal, i.e. they don't go to the server to see what's new!

Upvotes: 1

Davide Salerno
Davide Salerno

Reputation: 1

I resolved my problem with this (horrible) workaround: - I renamed the "index" action of IndexController to "home" - I setup a static route for home page (source) - I changed the htaccess to:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^(.*)home(/)?$ $1 [R=301,L]
RewriteRule ^(.*)index/(.*)$ $1$2 [R=301,L]
RewriteRule ^.*$ index.php [QSA,NC,L]

So now the home page is not duplicated because http://localhost/home/ is redirected to the base domain and for other controllers the index action, if it is called specifying the action name (/controller/index/param/value) is redirected to the desired URL (/controller/param/value/)

Upvotes: 0

TerryE
TerryE

Reputation: 10888

Here is your ruleset laid out readably

RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR] 
RewriteCond %{REQUEST_FILENAME} -d 
RedirectMatch ^(.*)/(index(/)?)$ http://localhost$1 
RewriteRule ^.*$                 -                        [NC,L] 
RewriteRule ^.*$                 index.php                [NC,L]

RedirectMatch is a mod_alias directive which severs the conds as from their rule. Also it's a lot less fraught not mixing mod_alias and mod_rewrite directives, so try:

RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR] 
RewriteCond %{REQUEST_FILENAME} -d 
RewriteRule ^                   -                        [L] 

RewriteRule ^(.*?)/index/?$     $1                       [R=301,L] 

RewriteRule ^.*$                index.php                [NC,L]

(updated following posters comments)

More footnotes

I tried this out on my VM which mirrors my hosting service, but having root access I can see the 'production' rewrite logs. This fails because the second rules still falls through to rule (3) which dispatchs to index.php. This then returms the full content but with a 301 status and without issuing a new location. If I change the [R=301] to [R=301,L] then it works fine as the server now issues a 301 with a Location header and the browser now retries with the new location.

The documentation states:

You will almost always want to use [R] in conjunction with [L] (that is, use [R,L]) because on its own, the [R] flag prepends http://thishost[:thisport] to the URI, but then passes this on to the next rule in the ruleset, which can often result in 'Invalid URI in request' warnings.

Upvotes: 0

Related Questions