fygesser
fygesser

Reputation: 93

Multiple RewriteRules for a single URL

I want to use multiple RewriteRules in .htaccess to modify a single URL, but only the last rule gets applied.

Example INPUT (link loaded by a browser):

http://example.com/aaa/foo/bar

.htaccess:

RewriteRule ^(.*)foo/(.*)$ $1nofoo/$2
RewriteRule ^(.*)bar(.*)$ $1nobar$2

EXPECTED OUTPUT (what Apache should actually look at):

http://example.com/aaa/nofoo/nobar

ACTUAL OUTPUT:

http://example.com/aaa/foo/nobar/

As you can see, only the last rule was applied. Is there any way to make it work the way I want? All suggestions are welcome.

PS. I want to avoid creating a static, ugly rule like

^(.*)foo/bar(.*) $1nofoo/nobar$2

I need all the modifications to work independently of each other.

UPDATE

So here is exactly what I am trying to achieve. I have some links to a backend server:

http://myserver.com/api/user/$userid/car/$carid/getSpeedRecordDetails
http://myserver.com/api/user/$userid/getUserDetails
http://myserver.com/api/car/$carid/getCarDetails

Where $userid and $carid are some unique 12-char-long strings.

And I want to transform them to these:

http://myserver.com/api/getSpeedRecordDetails.php?userid=$userid&carid=$carid
http://myserver.com/api/getUserDetails.php?userid=$userid
http://myserver.com/api/getCarDetails.php?carid=$carid

And I want to achieve it using the least RewriteRules possible (I am looking for a dynamic solution).

UPDATE #2

I love the SO community! Your patience and willingness to help is truly amazing :)

So the very reason why I am interested in modifying the URL using multiple RewriteRules is because I expect that my backend might soon need to implement hundreds (if not thousands) of user-friendly URLs, and mapping all of them individually would be a waste of time and money. Therefore, I want to take advantage of the fact that all the user-friendly URLs consist of repetitive chunks that can be easily translated. The calls below represent the general three types of the user-friendly URLs I need to manage. The only difference within each type is $userid, $carid, and XXXX_of_a_thousand_functions.

http://myserver.com/backend-api/user/$userid/car/$carid/first_of_a_thousand_functions.do
http://myserver.com/backend-api/user/$userid/second_of_a_thousand_functions.do
http://myserver.com/backend-api/car/$carid/sixteenth_of_a_thousand_functions.do

All of these calls (and remember, there will be hundreds, or even thousands of them) need to be translated into these:

http://myserver.com/backend-api/first_of_a_thousand_functions.php?USER_ID=$userid&CAR_ID=$carid
http://myserver.com/backend-api/second_of_a_thousand_functions.php?USER_ID=$userid
http://myserver.com/backend-api/sixteenth_of_a_thousand_functions.php?CAR_ID=$carid

Seeing that there is a simple pattern governing the translation (I hope it is also visible to you), I thought I could create some simple rules for translating different 'chunks' of the user-friendly URL into the internal URL. For example:

RewriteRule ^(.*)user/([A-Za-z0-9]+)/(.*)$ $1$3&USER_ID=$2

Would be responsible for translating the piece user/HSGRE8563LOS into &USER_ID=HSGRE8563LOS

And because some calls have more than one 'chunk' to process, I need to be able to use multiple RewriteRules on a single URL, which I hope somewhat correlates with the title of the question :)

UPDATE #3 - a future reference

So there are a couple of things I believe need to be said regarding this question.

Apparently .htaccess DOES by default apply all the rules that the conditions of are met. However, some CGI/Fast-CGI installations ruin it, resulting in the kind of behaviour depicted in the first example.

Also, one thing I have NEVER seen mentioned anywhere is that Apache applies the RewriteRules not in the order in which they are listed in .htaccess, but it starts 'scanning' the URL from its beginning, and as soons as the conditions for ANY of the rules are met, the URL gets modified accordingly.

Upvotes: 2

Views: 1858

Answers (1)

MrWhite
MrWhite

Reputation: 45829

After UPDATE#1... to internally rewrite from the stated friendly URLs using mod_rewrite. Try the following directives in the root .htaccess file:

RewriteEngine On

# http://myserver.com/api/user/$userid/car/$carid/getSpeedRecordDetails
RewriteRule ^api/user/(\w{12})/car/(\w{12})/(getSpeedRecordDetails)$ /api/$3.php?userid=$1&carid=$2 [L]

# http://myserver.com/api/user/$userid/getUserDetails
RewriteRule ^api/user/(\w{12})/(getUserDetails)$ /api/$2.php?userid=$1 [L]

# http://myserver.com/api/car/$carid/getCarDetails
RewriteRule ^api/car/(\w{12})/(getCarDetails)$ /api/$2.php?carid=$1 [L]

\w{12} matches a 12 char long string of letters (upper/lower), numbers and underscore. However, this should be made as restrictive as possible. eg. if a valid id is only numeric then \d{12} would be preferable.

UPDATE#2 The process is almost the same as above....

RewriteBase /backend-api

# http://myserver.com/backend-api/user/$userid/car/$carid/<any>.do
RewriteRule ^backend-api/user/(\w{12})/car/(\w{12})/(\w+)\.do$ $3.php?USER_ID=$1&CAR_ID=$2 [L]

# http://myserver.com/backend-api/user/$userid/<any>.do
RewriteRule ^backend-api/user/(\w{12})/(\w+)\.do$ $2.php?USER_ID=$1 [L]

# http://myserver.com/backend-api/car/$carid/<any>.do
RewriteRule ^backend-api/car/(\w{12})/(\w+)\.do$ $2.php?CAR_ID=$1 [L]

Note the use of RewriteBase. This allows the URL-path to be removed from the RewriteRule substitution.

If /backend-api exists as a physical directory then these rules could instead go in /backend-api/.htaccess. Then you could remove the RewriteBase directive and modify the RewriteRule pattern by removing the backend-api/ portion from near the start of the pattern.

UPDATE#3 - a future reference

Apparently .htaccess DOES by default apply all the rules that the conditions of are met. However, some CGI/Fast-CGI installations ruin it, resulting in the kind of behaviour depicted in the first example.

There are certainly some server (mis)configurations that appear to affect certain aspects of .htaccess/mod_rewrite, however, not in the way suggested earlier in your question and I have never encountered this directly myself. And this should have nothing to do with CGI/Fast-CGI. (?)

...Apache applies the RewriteRules not in the order in which they are listed in .htaccess, ...

Not sure exactly what you mean by this, but RewriteRules are processed "in the order in which they are listed in .htaccess" - this is fundamental to how mod_rewrite works. (However, different modules execute at different times, regardless of the order in .htaccess, but within each module the directives execute top-down, in order. eg. mod_rewrite executes before mod_alias (usually), so if you have a mod_alias Redirect before a mod_rewrite RewriteRule, the RewriteRule is still processed first - which is why it is a bad idea to mix redirects from both modules, as you can end up with confusing conflicts.)

...but it starts 'scanning' the URL from its beginning, and as soons as the conditions for ANY of the rules are met, the URL gets modified accordingly.

Exactly, in the order in which they appear in the file.

Note that it's the RewriteRule directives that are scanned (top - down), not the conditions (ie. RewriteCond directives) - in case that is what you were implying. If the RewriteRule pattern matches the URL-path then the preceding RewriteCond directives are processed and if all these pass then the substitution occurs. (Which is why it is always more efficient to match what you can with the RewriteRule and not rely totally on the RewriteCond directives - the RewriteRule is processed first, not the RewriteCond directives.)

Crucially, (and this might be where you are tripping up?), is that if you have multiple RewriteRule directives then the following RewriteRule directives match against the output/substitution of the previously matched RewriteRule (if any), not against the URL-path of the initial request. Only the first matched RewriteRule matches against the URL-path of the request. So, yes, RewriteRule directives do chain together.

The exception to this is the L (LAST) flag on the RewriteRule. This "breaks" the chain. Although not completely... it causes the current round of processing to stop, but then it all starts again from the top! Only when the URL passes through unchanged is processing finished. (However, in Apache 2.4 you do have the END flag - this does indeed halt processing completely.)

Upvotes: 1

Related Questions