How to stop .htaccess loop

The rule-set ends up in a loop. Let's see:

The request is http://localhost/Test/TestScript.php is redirected to http://localhost/Test/TestScript/, for the browser to show it, and finally is trying to be mapped back to the original resource.

The [L] flag in the rule doesn't stop the process, as many people think. The rewriting engine loops through the complete rule-set, rule by rule, and when a particular rule matches, it loops through the corresponding conditions, if any. As this process is repeated for each request and rules generate new requests, it is easy to enter an infinite loop.

That's what the message "The page isn't redirecting properly" means in this case.

Here are The Technical Details of this process

Some solutions:

I) The best and more practical one is to use directly the "pretty" URL in the initial request, mapping it silently to the resource. This is a one step process where the "pretty" URL is always displayed in the browser's address bar. One of the advantages of this option, is that nothing in the URI-path of the incoming URL has to exist.

  1. Request: http://localhost/Test/TestScript/
  2. Mapped internally to resource: http://localhost/Test/TestScript.php
Options +FollowSymlinks -MultiViews
RewriteEngine On
RewriteBase /
# Prevent loops
RewriteCond %{REQUEST_URI} !\.php  [NC]
# Map internally to the resource, showing the "pretty" URL in the address bar
RewriteRule  ^([^/]+)/([^/]+)/?   /$1/$2.php  [L,NC]

II) If that's not possible because there are already links pointing directly to the resource, one way to show the "pretty" URL but still get the data from the original request, is to make a visible and permanent redirect first, stripping the extension to display the "pretty" URL, and then an internal rewrite back to the original resource.

  1. Request: http://localhost/Test/TestScript.php,
  2. Permanent and visible redirect to: http://localhost/Test/TestScript/ the "pretty" URL,
  3. Internal and silent mapping back to: http://localhost/Test/TestScript.php, the original request.
Options +FollowSymlinks -MultiViews
RewriteEngine On
RewriteBase /

# Get the URI-path directly from THE_REQUEST variable
RewriteCond %{THE_REQUEST} ^(GET|HEAD)\s/([^/]+)/([^.]+)\.php [NC]
# Strip the extension and redirect permanently
RewriteRule  .*   /%2/%3/   [R=301,L,NC]

# Now the browser bar shows `http://localhost/Test/TestScript/`

# Prevent loops
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !\.php  [NC]
# Map internally to the original resource
RewriteRule  ^([^/]+)/([^/]+)/?   /$1/$2.php  [L,NC]

NOTES:

  1. The above options are to be placed in one .htaccess file at root directory, making sure mod_rewrite is enabled.
  2. Strings Test and TestScript are assumed to be dynamic and can be replaced with any name.
  3. Host name localhost is an example and can also be replaced with any other name.