Why does this RewriteRule produce infinite recursion? - apache

I am trying to do rather basic URL rewriting but cannot get it running properly.
I want to redirect all requests to wordpress.foobar.com to a local symlinked WordPress installation via my .htaccess file:
RewriteEngine On
RewriteCond %{SERVER_NAME} ^wordpress\.foobar\.com$
RewriteRule ^(.*) /html/wordpress/$1 [L]
While I can perfectly access foobar.com/html/wordpress, visiting wordpress.foobar.com will raise a 500 Internal Server Error.
Apache's error log will contain the following:
[error] [client 12.123.123.123] Request exceeded the limit of 10 internal redirects due to
probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get
a backtrace.
When I enable debug LogLevel I can see that Apache is trying to rewrite favicon.ico infinitely, always appending another layer of /html/wordpress/ until the recursion limit is reached.
What's wrong with this rewrite?

Use:
RewriteCond %{SERVER_NAME} ^wordpress\.foobar\.com$
RewriteCond %{REQUEST_URI} !/html/wordpress/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /html/wordpress/$1 [L]
With -f and -d test, you do not redirect existing files and directories. This way, you avoid the problems with favicon.ico (if you have that file, I suggest you add if this is not the case).

When you get the error Request exceeded the limit of 10 internal redirects due to
probable configuration error., ask yourself the question: How did I design my rules to terminate at some point, and why does it not terminate.
In your case, your rule matches everything. The status of the condition will not change either, so if your rule matches one time, it will match an infinite amount of times. This might work on some setups if you rewrite to a file that does exist. It is a safer bet to just prevent it from rewriting more than once by testing if /html/wordpress/ already is in the url:
RewriteCond %{SERVER_NAME} ^wordpress\.foobar\.com$
RewriteCond %{REQUEST_URI} !/html/wordpress/
RewriteRule ^(.*)$ /html/wordpress/$1 [L]
Besides this, I am not sure if wordpress will recognize your rewritten url. That is for you to figure out.

Related

How to redirect url to the url with htaccess

I didn't know how to question this but my problem is like this
I have written a rule for a url
RewriteRule ^mysite.com.pk/([a-zA-Z0-9]+) index.php?store=$1
RewriteRule ^mysite.com.pk/([a-zA-Z0-9]+)/products index.php?store=$1&view=products
RewriteRule ^mysite.com.pk/([a-zA-Z0-9]+)/products/([0-9]+) index.php?store=$1&view=products&category=$2
RewriteRule ^mysite.com.pk/([a-zA-Z0-9]+)/single/([0-9]+) index.php?store=$1&view=single&product=$2
I am new to htaccess so I don't know much about it. Is there a way that even if a old url comes like index.php?store=abc&view=single&product=123
this will be redirected to a new one like mysite.com.pk/abc/single/123
You have to make some small modifications to your proposed rules to get them to work. And you have to add additional rewriting rules to achieve what you actually ask: redirecting "old" URLs to your new syntax.
This is probably what you are looking for:
RewriteEngine on
RewriteRule ^/?([a-zA-Z0-9]+)$ index.php?store=$1 [END]
RewriteRule ^/?([a-zA-Z0-9]+)/products$ index.php?store=$1&view=products [END]
RewriteRule ^/?([a-zA-Z0-9]+)/products/([0-9]+)$ index.php?store=$1&view=products&category=$2 [END]
RewriteRule ^/?([a-zA-Z0-9]+)/single/([0-9]+)$ index.php?store=$1&view=single&product=$2 [END]
# index.php?store=xyz&view=single&product=123
RewriteCond %{QUERY_STRING} store=([^&]+)&?
RewriteCond %{QUERY_STRING} view=single&?
RewriteCond %{QUERY_STRING} product=([^&]+)&?
RewriteRule /?index.php$ /%1/single/%3 [END,R=301]
# index.php?store=xyz&view=products&product=123
RewriteCond %{QUERY_STRING} store=([^&]+)&?
RewriteCond %{QUERY_STRING} view=products&?
RewriteCond %{QUERY_STRING} category=([^&]+)&?
RewriteRule /?index.php$ /%1/products/%3 [END,R=301]
# index.php?store=xyz&view=products
RewriteCond %{QUERY_STRING} store=([^&]+)&?
RewriteCond %{QUERY_STRING} view=products&?
RewriteRule /?index.php$ /%1/products [END,R=301]
# index.php?store=xyz
RewriteCond %{QUERY_STRING} store=([^&]+)&?
RewriteRule /?index.php$ /%1 [END,R=301]
Those rules should work the same in a .htaccess style file and in the real http host configuration. Please see the note below about that.
If you are using an old version of the apache http server then you may have to replace the END flag by the L flag. Try that if you receive an "internal server error" (http status 500) and the server complains about the END flag in the error log file. You may have to add some additional conditions to prevent an endless rewriting loop in that case.
Please note that I did not test that rule set. I hope it does not contain any silly mistakes and you certainly will have to test it.
All of the above assumes that mysite.com.pk is meant to match your host name ("domain"). But that will not work, since the RewriteRules work on the request path, not the full URL. If you want to limit the application of the rules to a specific host, then you can add a leading condition to stop the rewriting process:
RewriteCond %{HTTP_HOST} !^mysite\.com\.pk`
RewriteRule .* - [END]
And a general hint: you should always prefer to place such rules inside the http servers host configuration instead of using .htaccess style files. Those files are notoriously error prone, hard to debug and they really slow down the server. They are only provided as a last option for situations where you do not have control over the host configuration (read: really cheap hosting service providers) or if you have an application that relies on writing its own rewrite rules (which is an obvious security nightmare).

htaccess pretty urls not working

Folder structure:
- assets
- all css / js
- calsses
- all models, db ant etc
- views
- admin
- app
- index.php
- customers.php
.......
my .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^(www.)?localhost:8080$
RewriteRule ^(.*)$ /views/$1
RewriteRule ^(/)?$ /views/index.php [L]
address : localhost:8080/app/ - working fine, but then I try to add pretty url for example in my customers.php - localhost:8080/app/customers.php?id=5 change to localhost:8080/app/customers/id/5
htaccess added new line:
RewriteRule /id/(.*) customers.php?id=$1
It's not working, it always return 500 Internal Server Error there could be the problem?
plus Need all urls without .php extend
You'd have to include those conditions for every rule. You'd be better off just rewriting everything to, say views/router.php then using PHP to include the different controllers, or serve a 404 when the URL isn't valid.
RewriteRule !^views/router\.php$ views/router.php [NS,L,DPI]
I agree with Walf in that handling routes through a router class is a better idea (especially in the long run!) than using .htaccess redirects.
However, as your question seems to be more about why is this not working than about how you should do it, here is an explanation for what is going on.
I will be using these URLs as examples:
localhost:8080
localhost:8080/app
localhost:8080/app/customers/id/5
Your first rule:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^(www.)?localhost:8080$
RewriteRule ^(.*)$ /views/$1
As you intended, this RewriteRule will match any URL which is not a file, not a directory, and made to localhost:8080.
localhost:8080 # not matched because it leads to a directory.
localhost:8080/app -> localhost:8080/views/app
localhost:8080/app/customers/id/5 -> localhost:8080/views/app/customers/id/5
Your next rule:
RewriteRule ^(/)?$ /views/index.php [L]
It is important to realize that RewriteCond statements apply only to the first RewriteRule following them, thus all that is being checked here is the path.
Side note: ^(/)?$, as you are not using $1, can be simplified to ^/?$.
localhost:8080 -> localhost:8080/views/index.php
localhost:8080/views/app # not matched
localhost:8080/views/app/customers/id/5 # not matched
As the L flag is specified, Apache will immediately stop the current iteration and start matching again from the top. The documentation is badly worded. Thus, localhost:8080/views/index.php will be run through the first rule, fail to match, be run through this rule, fail to match, and then as no other rules exist to check (yet) no rewrite will be done.
Now lets look at what happens when you add your broken rule.
RewriteRule /id/(.*) customers.php?id=$1
There are a few problems here. First, as you don't require that the URL start with /id/ the rule will always match a URL that contains /id/, even if you have already rewritten the URL. If you amended this by using ^/id/(.*), then you would still have issues as the string that the rewrite RegEx is tested against has leading slashes removed. Lastly and most importantly, customers.php does not exist in your root directory.
localhost:8080/views/index.php # not matched
localhost:8080/views/app # not matched
localhost:8080/views/app/customers/id/5 -> localhost:8080/customers.php?id=5
This is the last rule in your file currently, so now Apache will start over. customers.php does not exist in your directory, so it will be rewritten to views/customers.php. No other rules matched, but the URL has changed and so Apache will start over again, as /views/customers.php does not exist, it will be rewritten to /views/views/customers.php ... This pattern will repeat until you hit the maximum iteration limit and Apache responds with a 500 error.
You can solve this several ways. Here would be my preferred method, but only if you cannot use a router.
RewriteEngine on
# Rewrite the main page, even though it is a directory
RewriteRule ^/?$ views/index.php [END]
# Don't rewrite any existing files or directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .? - [S=999,END]
RewriteRule ^app/?$ views/app/index.php [END]
RewriteRule ^app/id/(.*)$ views/app/customers.php?id=$1 [END]
TL;DR Use a PHP based router. .htaccess rules can be incredibly confusing.
Please refer to the question, How to make Clean URLs
I think this is what you needed.
you can use RewriteRule ^(.*)$ index.php [QSA,L]
Having another crack.
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{HTTP_HOST} !^(?:www\.)?localhost:8080$ [OR]
RewriteCond $0 =views
RewriteRule [^/]* - [END]
RewriteRule ^(app|admin)/([^/]+) views/$1/$2.php [DPI,END]
RewriteRule ^(app|admin)/?$ views/$1/index.php [DPI,END]
You may have to use L instead of END flags if your Apache is older. Set up an ErrorDocument for 404s, too.
Don't muck around with query strings, just parse $_SERVER['REQUEST_URI'] in PHP, e.g. start by exploding it on /. Then you'll have all the parameters of the original pretty URL. You can do that part in an include so each controller can reuse the same code.
I tried your structure and .htaccess file myself and found an endless loop in the apache logs. I bet you got something like this:
Mon Nov 28 19:57:32.527765 2016] [core:error] [pid 10] [client 172.18.0.1:35048] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
I could fix it by adding the last rule like:
RewriteRule id/(.*) /views/app/customers.php?id=$1
The leading / is not needed for the match and the target needs the full path. Note that I got the id double (e.g. 123/123) on the url: http://localhost:8080/id/123.
This is caused by one of the 2 previous rules (removing them fixes it) so you might need to change them.
Here is what you want :
RewriteEngine On
RewriteBase /app/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^\/?$ views/index.php [L]
RewriteRule ^([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)\/?$ views/$1.php?$2=$3 [L]
RewriteRule ^([a-zA-Z0-9]+)\/?$ views/$1.php [L]

Apache RewriteRule Issues - Different behaviour local compared to server

I'm having some very weird issues with a simple RewriteRule that is working as it should locally but when deployed to the server I'm getting 500 errors with the text:
Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
The following is the code that I am using:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/(foo|bar|baz)/?
RewriteCond %{REQUEST_URI} !^/410/index.php$
RewriteRule ^.*$ /410/index.php? [L]
</IfModule>
What I want is anything other than /foo, /bar, or /baz to show the page located at /410/index.php
Yes, mod_rewrite.c is installed/enabled on the server.
Since this is for an .htaccess file try removing the slash preceding the "410" in the rewrite rule. Also if you check to see if it's a real file that should stop the endless redirects back to the index page.
I'm going to take a guess and say there is the slight chance (and I couldn't find documentation on this) that the request string you're rewriting with the ? to flush the existing query string might not being matched.
RewriteEngine On
#check if it's a not a real file
RewriteCond %{REQUEST_FILENAME} !-f
#Check if the request doesn't start with foo, bar, or baz
RewriteCond %{REQUEST_URI} !^/(foo|bar|baz)
#Check if uri doesn't only contain the /410/index.php
RewriteCond %{REQUEST_URI} !^/410/index.php\??$
#rewrite the file
RewriteRule ^.*$ 410/index.php? [L]

mod_rewrite ignores [L]

I want to be able to rewrite this
http://localhost/.../identicon/f528764d624db129b32c21fbca0cb8d6.png
to
http://localhost/.../identicon.php?hash=f528764d624db129b32c21fbca0cb8d6
so I add to the /.../.htaccess so this is it:
RewriteEngine On
RewriteRule ^resource/ - [L]
RewriteRule ^identicon/(.+)\.png$ identicon.php?hash=$1 [QSA,L]
RewriteRule ^(.*)$ index.php?t=$1 [QSA,L]
Which doesn't work for some reason because it redirects it to index.php?t=identicon.php; even though the L flag is set! Why?
Add a condition to the last rule to exclude requests that can be mapped to existing files:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?t=$1 [QSA,L]
That is necessary because the L flag generates an internal redirect with the new URL as the request URL:
Remember, however, that if the RewriteRule generates an internal redirect (which frequently occurs when rewriting in a per-directory context), this will reinject the request and will cause processing to be repeated starting from the first RewriteRule.
(Not correct answer; left for reference)
I just figured out what may be the issue - it's something that thwarted me for a long time.
Depending on your server settings, it very well may be interpreting identicon/xxx.png as a request to identicon.php/xxx.png, assuming that the PHP extension is what you wanted. Try going to /index instead of /index.php - if it loads the PHP file, this is the issue affecting you.
This is the MultiViews Apache option, and it's stupid, but it has to be enabled specifically. Go into your site configuration file and see where it is enabled, and remove it.
If you don't have total control over your server configuration, the following may work in .htaccess (depending, ironically, on your server configuration).
Options -Multiviews

Hidden features of mod_rewrite

There seem to be a decent number of mod_rewrite threads floating around lately with a bit of confusion over how certain aspects of it work. As a result I've compiled a few notes on common functionality, and perhaps a few annoying nuances.
What other features / common issues have you run across using mod_rewrite?
Where to place mod_rewrite rules
mod_rewrite rules may be placed within the httpd.conf file, or within the .htaccess file. if you have access to httpd.conf, placing rules here will offer a performance benefit (as the rules are processed once, as opposed to each time the .htaccess file is called).
Logging mod_rewrite requests
Logging may be enabled from within the httpd.conf file (including <Virtual Host>):
# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2
Common use cases
To funnel all requests to a single point:
RewriteEngine on
# ignore existing files
RewriteCond %{REQUEST_FILENAME} !-f
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# map requests to index.php and append as a query string
RewriteRule ^(.*)$ index.php?query=$1
Since Apache 2.2.16 you can also use FallbackResource.
Handling 301/302 redirects:
RewriteEngine on
# 302 Temporary Redirect (302 is the default, but can be specified for clarity)
RewriteRule ^oldpage\.html$ /newpage.html [R=302]
# 301 Permanent Redirect
RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
Note: external redirects are implicitly 302 redirects:
# this rule:
RewriteRule ^somepage\.html$ http://google.com
# is equivalent to:
RewriteRule ^somepage\.html$ http://google.com [R]
# and:
RewriteRule ^somepage\.html$ http://google.com [R=302]
Forcing SSL
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R,L]
Common flags:
[R] or [redirect] - force a redirect (defaults to a 302 temporary redirect)
[R=301] or [redirect=301] - force a 301 permanent redirect
[L] or [last] - stop rewriting process (see note below in common pitfalls)
[NC] or [nocase] - specify that matching should be case insensitive
Using the long-form of flags is often more readable and will help others who come to read your code later.
You can separate multiple flags with a comma:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Common pitfalls
Mixing mod_alias style redirects with mod_rewrite
# Bad
Redirect 302 /somepage.html http://example.com/otherpage.html
RewriteEngine on
RewriteRule ^(.*)$ index.php?query=$1
# Good (use mod_rewrite for both)
RewriteEngine on
# 302 redirect and stop processing
RewriteRule ^somepage.html$ /otherpage.html [R=302,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# handle other redirects
RewriteRule ^(.*)$ index.php?query=$1
Note: you can mix mod_alias with mod_rewrite, but it involves more work than just handling basic redirects as above.
Context affects syntax
Within .htaccess files, a leading slash is not used in the RewriteRule pattern:
# given: GET /directory/file.html
# .htaccess
# result: /newdirectory/file.html
RewriteRule ^directory(.*)$ /newdirectory$1
# .htaccess
# result: no match!
RewriteRule ^/directory(.*)$ /newdirectory$1
# httpd.conf
# result: /newdirectory/file.html
RewriteRule ^/directory(.*)$ /newdirectory$1
# Putting a "?" after the slash will allow it to work in both contexts:
RewriteRule ^/?directory(.*)$ /newdirectory$1
[L] is not last! (sometimes)
The [L] flag stops processing any further rewrite rules for that pass through the rule set. However, if the URL was modified in that pass and you're in the .htaccess context or the <Directory> section, then your modified request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand this, it often looks like your [L] flag had no effect.
# processing does not stop here
RewriteRule ^dirA$ /dirB [L]
# /dirC will be the final result
RewriteRule ^dirB$ /dirC
Our rewrite log shows that the rules are run twice and the URL is updated twice:
rewrite 'dirA' -> '/dirB'
internal redirect with /dirB [INTERNAL REDIRECT]
rewrite 'dirB' -> '/dirC'
The best way around this is to use the [END] flag (see Apache docs) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent passes). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag.
For earlier versions, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine.
# Only process the following RewriteRule if on the first pass
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ...
Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
if you need to 'block' internal redirects / rewrites from happening in the .htaccess, take a look at the
RewriteCond %{ENV:REDIRECT_STATUS} ^$
condition, as discussed here.
The deal with RewriteBase:
You almost always need to set RewriteBase. If you don't, apache guesses that your base is the physical disk path to your directory. So start with this:
RewriteBase /
Other Pitfalls:
1- Sometimes it's a good idea to disable MultiViews
Options -MultiViews
I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active, because one of its properties is to try and 'guess' an extension to a file that it thinks I'm looking for.
I'll explain:
Suppose you have 2 php files in your web dir, file1.php and file2.php and you add these conditions and rule to your .htaccess :
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1
You assume that all urls that do not match a file or a directory will be grabbed by file1.php. Surprise! This rule is not being honored for the url http://myhost/file2/somepath. Instead you're taken inside file2.php.
What's going on is that MultiViews automagically guessed that the url that you actually wanted was http://myhost/file2.php/somepath and gladly took you there.
Now, you have no clue what just happened and you're at that point questioning everything that you thought you knew about mod_rewrite. You then start playing around with rules to try to make sense of the logic behind this new situation, but the more you're testing the less sense it makes.
Ok, In short if you want mod_rewrite to work in a way that approximates logic, turning off MultiViews is a step in the right direction.
2- enable FollowSymlinks
Options +FollowSymLinks
That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.
Equation can be done with following example:
RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]
Dynamic Load Balancing:
If you use the mod_proxy to balance your system, it's possible to add a dynamic range of worker server.
RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
A better understanding of the [L] flag is in order. The [L] flag is last, you just have to understand what will cause your request to be routed through the URL parsing engine again. From the docs (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (emphasis mine):
The [L] flag causes mod_rewrite to stop processing the rule set. In
most contexts, this means that if the rule matches, no further rules
will be processed. This corresponds to the last command in Perl, or
the break command in C. Use this flag to indicate that the current
rule should be applied immediately without considering further rules.
If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of
how the rules are processed. The simplified form of this is that once
the rules have been processed, the rewritten request is handed back to
the URL parsing engine to do what it may with it. It is possible that
as the rewritten request is handled, the .htaccess file or <Directory>
section may be encountered again, and thus the ruleset may be run
again from the start. Most commonly this will happen if one of the
rules causes a redirect - either internal or external - causing the
request process to start over.
So the [L] flag does stop processing any further rewrite rules for that pass through the rule set. However, if your rule marked with [L] modified the request, and you're in the .htaccess context or the <Directory> section, then your modifed request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand what happened, it looks like your first rewrite rule with the [L] flag had no effect.
The best way around this is to use the [END] flag (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent reparsing). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag. In this case, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine. Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
Another great feature are rewrite-map-expansions. They're especially useful if you have a massive amout of hosts / rewrites to handle:
They are like a key-value-replacement:
RewriteMap examplemap txt:/path/to/file/map.txt
Then you can use a mapping in your rules like:
RewriteRule ^/ex/(.*) ${examplemap:$1}
More information on this topic can be found here:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc
mod_rewrite can modify aspects of request handling without altering the URL, e.g. setting environment variables, setting cookies, etc. This is incredibly useful.
Conditionally set an environment variable:
RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]
Return a 503 response:
RewriteRule's [R] flag can take a non-3xx value and return a non-redirecting response, e.g. for managed downtime/maintenance:
RewriteRule .* - [R=503,L]
will return a 503 response (not a redirect per se).
Also, mod_rewrite can act like a super-powered interface to mod_proxy, so you can do this instead of writing ProxyPass directives:
RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]
Opinion:
Using RewriteRules and RewriteConds to route requests to different applications or load balancers based on virtually any conceivable aspect of the request is just immensely powerful. Controlling requests on their way to the backend, and being able to modify the responses on their way back out, makes mod_rewrite the ideal place to centralize all routing-related config.
Take the time to learn it, it's well worth it! :)