Baffled by mod_rewrite behavior

I am fairly new to mod_rewrite and I have read numerous tutorials, my apache manual, and dklynn’s page. I must be misunderstanding some of the specs and misapplying them.

I am just experimenting with my mvc sandbox site on my local machine and want to rewrite all URIs to be in a single GET variable:


RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^mod.*$ index.php?rt=test [L]
RewriteRule ^(.*)$ index.php?rt=$1 

If the URI begins with “mod,” redirect to the root controller with “test” as the value of the rt GET variable. Otherwise, use the entire URI into the value of the rt GET variable. This does not work as expected. I am logging the entire value of the GET array to check. //mysite/mod sends a value of “index.php” with the rt variable, when it should be sending “test.” So, in testing, I commented out the second RewriteRule and the redirection worked as expected. Therefore I figured that I should add the L(ast) flag to stop processing after the first rule. I uncommented the second rule and tried again and, once again, “index.php” was sent with rt. I don’t understand what I am doing wrong. Anyone have any insight? :frowning:

smola,

You’ve NOT read (or understood) the danger of the :kaioken: EVERYTHING :kaioken: atom! The problem you’re experiencing is that (.*) is matching index.php and redirecting in a loop.

Regards,

DK

Ok, but I want it to catch EVERYTHING. So then the problem remains, how do I stop the loop? I have tried add the L flag to both lines and that does not solve the problem.

Also, why is the first L flag not stopping the processing? Isn’t that what the flag is for?

Alright, I went back and re-read your page carefully as I had not figured out that the rules are applied again after the rewrite. I was under the impression that the rules are applied a single time and that’s that. So, I guess the idea is that the rules rewrite until the rewritten URL doesn’t match any of them. That’s why the L flag is appearing to not work. Actually, the first run through IS applying the L flag. But then the rules are applied to that rewrite (//mysite/index.php?rt=index.php) and the first rule gets skipped over, nullifying the L flag effect I expected. Then the second rule is applied and catches index.php but NOT the query string because the QSA flag is not present. Am I right?

If that’s the case, I don’t think there is any way to accomplish what the test was supposed to do. I can’t have a rule to catch EVERYTHING and then another rule that is more specific. The only way would be to have Apache process the rules a single time, and I imagine that isn’t possible. So here’s my question, would the “everything atom” keep Apache stuck in an infinite loop? How does a page ever get served?

smola,

If you WANT to catch EVERYTHING, then you must provide an escape to prevent looping … like checking that the EVERYTHING is NOT the file you are redirecting to.

Indeed, the Last flag stops processing but the NEW URL is then assessed and the request cycle goes back to the .htaccess file to reprocess the directives, i.e., restarting the mod_rewrite processing.

As for the query string, it keeps getting destroyed by the NEW query string (same thing time after time - it loops with the URI). Had you been using QSA, you would have had ?rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}&rt={whatever}. How much sense would that have made?

How to avoid looping? Didn’t you read that, too? Oh, well, use a RewriteCond to test for (NOT) the redirection URI before the RewriteRule’s capture all garbage and redirect. Simple enough, isn’t it?

Regards,

DK

Please, be patient with me, I am new to this and just began experimenting this weekend. While documentation and tutorials are good, they cannot cover all situations and nothing is a better substitute for an actual person. I apologize that, yes, I did not pay close enough attention to your “Specificity” section to stop the looping.

Simple enough sure, but trying to prevent the loop failed for me. I thought, “OK, the first redirect creates the query string rt=test, so just test for that.” So I did:

RewriteCond %{QUERY_STRING} !(rt=test)

and it didn’t work. Then i figured, if the rt= is there at all I want it to stop, so I got rid of the “test” part and still didn’t work, though I didn’t really expect that it would if the first one didn’t work. So, what am I doing wrong this time?

Btw, I also removed the parentheses as I realized they were unnecessary after reading more about the negation symbol and still behavior is not what I expect:

RewriteCond %{QUERY_STRING} !rt=

Smola,

Sorry, mate! I try to make everything as clear as I can but have been known to get bogged down in the details (and exceptions). :bawling:

Now, what you’ve shown to test the query string looks okay to me (albeit it doesn’t need the parentheses) so I’d have to ask that you provide ALL your .htaccess code next time.

Let’s look at the previous code, though:

RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^mod.*$ index.php?rt=test [L]
RewriteRule ^(.*)$ index.php?rt=$1

First, the file and directory checks only apply to the FIRST RewriteRule.

Second, the (.*) is, as previously stated, the most ABUSED and most overused regex there is simply because that’s all that many know to use to make a RewriteRule match. Of course, that’s not the case with you because you DO want to match EVERYTHING but you failed to provide an exit strategy to the loop that it creates. It will even redirect mod{whatever} to index.php before starting the loop - if it were the first RewriteRule! Consider:

RewriteEngine on
# redirect mod{whatever} to index.php?rt=test
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^mod.*$ index.php?rt=test [L]

# redirect EVERYTHING to index.php - whoops! 
# This supercedes the first RewriteRule, doesn't it?
# prevent loop by checking the {REQUEST_URI}
RewriteCond %{REQUEST_URI} !^index\\.php [OR]
# OR (vs the default AND)
# prevent loop by checking the {QUERY_STRING} for rt=
RewriteCond %{QUERY_STRING} !rt=
RewriteRule ^(.*)$ index.php?rt=$1

I’m actually very pleased that you “got it” to the point of knowing how to test for the key in the query string (although, perhaps, not WHERE to insert that line of code?).

Please note, though, that the file and directory checks will have to be made for the SECOND RewriteRule just as they were made for the first as the second one will definitely capture all existing files and directories and redirect them to your index.php script as you had it.

Finally, you will be able to SEE the redirection if you test with the R=301 flag as that forces the redirection to be visible in the browser’s location textbox - but remember to remove it for your production scrips if you don’t want the redirection to be visible.

Regards,

DK

Sir, I wish I could shake your hand! I very much appreciate the time you’ve spent on this matter (especially this last reply!). Although very frustrating, spending time researching tutorials, explanations and examples on your page, and your personal assistance have resulted in a wealth of knowledge on mod_rewrite and it’s specificity.

Thanks again, and I’m sure you’ll be seeing future posts from me in the future! :smiley:

Smola,

I see that you beat me with the comment on the () in the RewriteCond. :tup:

I’m very pleased that you’ve learned a lot here - that’s my goal! I trust that you will continue the tradition of helping others understand what was once “difficult” for you and of which you’re now approaching the “Master” stage.

Regards,

DK