Why does this cause an infinite request loop?

前端 未结 2 630
孤独总比滥情好
孤独总比滥情好 2020-12-03 19:16

Earlier today, I was helping someone with an .htaccess use case, and came up with a solution that works but can\'t quite figure it out myself!

He wanted

相关标签:
2条回答
  • 2020-12-03 19:56

    The solution below worked for me.

    RewriteEngine on
    RewriteBase /
    
    #rule1
    #Guard condition: only if the original client request was for index.php
    RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php [NC]
    RewriteCond %{QUERY_STRING} ^id=(\d+)&cat=(\d+)$ [NC]
    RewriteRule . /index/%1/%2/? [L,R]
    
    #rule 2
    RewriteRule ^index/(\d+)/(\d+)/$ /index.php?id=$1&cat=$2 [L,NC]
    

    Here is what I think is happening

    From the steps you quoted above

    1. Browse to index.php?id=3&cat=5
    2. See the location bar read index/3/5/
    3. Have the content served from index.php?id=3&cat=5

    At Step 1, Rule 1 matches and redirects to location bar and fulfills Step 2.

    At Step 3, Rule 2 now matches and rewrites to index.php.

    The rules are rerun, for the reasons David stated, but since THE_REQUEST is immutable once set to the original request, it still contains /index/3/5 so Rule 1 does not match.

    Rule 2 does not match either and the result of index.php is served.

    Most other variables are mutable e.g. REQUEST_URI. Their modification during rule processing, and the incorrect expectation that the pattern matches are against the original request is a common reason for infinite loops.

    Its feels quite esoteric sometimes, but I am sure there is a logical reason for its complexity :-)

    EDIT

    Surely there are two distinct requests

    There are 2 client requests, the original one from Step1 and the one from the external redirect in step 2.

    What I glossed over above is that when Rule 2 matches on the second request, it is rewritten to /index.php and causes an internal redirect. This forces the .htaccess file for / directory to be loaded again (it could easily have been another another directory with different .htaccess rules) and Re-run all the rules again.

    So... why would this result in a request loop when I take out the first rule?

    When the rules are re-run, the first rule now unexpectedly matches, as a result of Rule2's rewrite, and does a redirect, causing an infinite loop.

    David's answer does contain most of this information and is what I meant "for the reasons David stated".

    However, the main point here is that you do need the extra condition, either your condition, which stops further rule processing on internal redirects, or mine, which prevents rule 1 from matching, is necessary to prevent the infinite loop.

    0 讨论(0)
  • 2020-12-03 20:14

    Without being able to tinker with your setup, I can't say for sure, but I believe this problem is due to the following relatively arcane feature of mod_rewrite:

    When you manipulate a URL/filename in per-directory context mod_rewrite first rewrites the filename back to its corresponding URL (which is usually impossible, but see the RewriteBase directive below for the trick to achieve this) and then initiates a new internal sub-request with the new URL. This restarts processing of the API phases.

    (source: mod_rewrite technical documentation, I highly recommend reading this)

    In other words, when you use a RewriteRule in an .htaccess file, it's possible that the new, rewritten URL maps to an entirely different directory on the filesystem, in which case the .htaccess file in the original directory wouldn't apply anymore. So whenever a RewriteRule in an .htaccess file matches the request, Apache has to restart processing from scratch with the modified URL. This means, among other things, that every RewriteRule gets checked again.

    In your case, what happens is that you access /index/X/Y/ from the browser. The last rule in your .htaccess file triggers, rewriting that to /index.php?id=X&cat=Y, so Apache has to create a new internal subrequest with the URL /index.php?id=X&cat=Y. That matches your earlier external redirect rule, so Apache sends a 302 response back to the browser to redirect it to /index/X/Y/. But remember, the browser never saw that internal subrequest; as far as it knows, it was already on /index/X/Y/. So it looks to you as though you're being redirected from /index/X/Y/ to that same URL, triggering an infinite loop.

    Besides the performance hit, this is probably one of the better reasons that you should avoid putting rewrite rules in .htaccess files when possible. If you move these rules to the main server configuration, you won't have this problem because matches on the rules won't trigger internal subrequests. If you don't have access to the main server configuration files, one way you can get around it (EDIT: or so I thought, although it doesn't seem to work - see comments) is by adding the [NS] (no subrequest) flag to your external redirect rule,

    RewriteRule ^index\.php$ http://example.com/index/%1/%2/? [L,R,NS]
    

    Once you do that, you should no longer need the first rule that checks the REDIRECT_STATUS.

    0 讨论(0)
提交回复
热议问题