Forum Moderators: phranque

Message Too Old, No Replies

Running multiple rules on a REQUEST URI

         

csdude55

11:49 pm on Apr 13, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I'm getting stuck here and I'm not sure what the logic is, so I'm hoping for some advice.

Let's say I have a REQUEST_URI like:

blah/bar/example/12345

This rule matches and rewrites:

RewriteRule ^blah/(foo|bar)/(.*) /blah/$2?topic=$1 [NC,QSA]


I didn't use [L], so it should continue on to the next rule, right?

The question is, does this change the value of REQUEST_URI? So the next rule would use the new result (/blah/example/12345?topic=bar), even though the URL didn't actually change?

Meaning, should this next rule match?

# this wouldn't match the original URL, but should match the rewritten URL
RewriteRule ^blah/[^/]+/([0-9]+)/?$ /blah/?id=$1 [NC,QSA]

phranque

12:30 am on Apr 14, 2020 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



I'm getting stuck here and I'm not sure what the logic is

Rewrite rules are applied to the results of previous rewrite rules, in the order in which they are defined in the config file. The URL-path or file-system path (see "What is matched?", above) is completely replaced by the Substitution and the rewriting process continues until all rules have been applied, or it is explicitly terminated by an L flag, or other flag which implies immediate termination, such as END or F.

source: https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewriterule

The question is, does this change the value of REQUEST_URI? So the next rule would use the new result (/blah/example/12345?topic=bar), even though the URL didn't actually change?

yes.
if you want to see the url requested by the browser, you can use THE_REQUEST.

Meaning, should this next rule match?

assuming $2 ends up with something that matches:
[^/]+/([0-9]+)/?

[edited by: phranque at 1:21 am (utc) on Apr 14, 2020]

lucy24

12:58 am on Apr 14, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



does this change the value of REQUEST_URI?
Yes: that’s a key difference between REQUEST_URI (“whatever URI we are working with at this particular instant”) and THE_REQUEST (“what the visitor originally asked for, and will see in their address bar").

But you will be very careful with these [L]-less rules, won't you? ;)

csdude55

4:07 am on Apr 14, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



But you will be very careful with these [L]-less rules, won't you? ;)

I'm still playing at this point, so I'll test the heck out of anything I do long before it goes live ;-) I'm honestly having a bit of creative writer's block and can't decide how to design the next section, so I'm playing with other back-end stuff until inspiration strikes.

I'm still playing with the section I'd mentioned earlier, where (foo|bar) were optional. I still haven't gotten it to work right, so now I'm toying with the idea of one rule to set ?topic= and removing it from the REQUEST_URI, then the rest of the rules don't have to worry about allowing for it.

lucy24

4:29 pm on Apr 14, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



one rule to set ?topic= and removing it from the REQUEST_URI
Careful! Although THE_REQUEST applies to the whole thing--including query string if you've got old-fashioned URLs that include it in visible form--the internal REQUEST_URI doesn’t. For that you have to use QUERY_STRING (which here doesn’t care whether the query is visible or not).

w3dk

10:18 pm on Apr 15, 2020 (gmt 0)

10+ Year Member Top Contributors Of The Month



Let's say I have a REQUEST_URI like:


To clarify from the OP... you appear to be referring to the requested URL-path, not strictly REQUEST_URI. REQUEST_URI is a server variable that contains the full URL-path. HOWEVER, your examples are not using this server variable, they are referencing the URL-path matched by the RewriteRule pattern. Whilst these are similar - they are not the same thing. Although both are updated accordingly after each RewriteRule.

(Just to add... the MWL htaccess tester that's been referenced quite a bit recently does not appear to handle this "chaining" of rules... each rule looks at the URL from the initial request, which is not correct.)

csdude55

6:40 am on Apr 16, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I'm trying something similar with another section, and it's causing an infinite loop... so I'm hoping you guys can explain this one, too.

I have 14 rules where the match ends with ?/$... the user may or may not have the trailing / in there. So to simplify things, I added this rule before them:

RewriteRule ^(.+)/$ /$1

The theory was that if the url ends with / then it would rewrite it without the /, then move on to the next rule. But instead, I just end up in a loop.

Even with the /?$ back in place on all of the rules, this one still ends up in a loop.

If I make it redirect with [R] it would probably work, but I don't want that; I just want the system to pretend as if it's not there for the sake of rewriting future rules to take the last section of the url and make it a GET param (and I don't want the / in the param).

phranque

7:43 am on Apr 16, 2020 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



there's always the option of mod_rewrite logging...

lucy24

3:38 pm on Apr 16, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



These aren’t real, physical directories, are they?

I’m not sure this is the right approach in any case. When you’re rewriting internally, you should be heading in the direction of real, physical files (like the ubiquitous CMS rewrite to index.php, where index.php really exists and nothing else does). Rewriting to anything extensionless, even temporarily, strikes me as fraught with peril.

But it will help if you give an example of the rule in action, and show what happens. At some point there must be an [L]; otherwise what happens at the end with that now-extensionless /blahblah request?

csdude55

5:31 pm on Apr 16, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



there's always the option of mod_rewrite logging...

@phranque, that's actually something I meant to mention earlier... in WHM I changed LogLevel to "alert", and that told me about code errors (like once when I left out a ]). But after that my error log doesn't seem to be working at all; the last entry was April 10! I know that I've had plenty of errors since then, but nothing in the log.

My options for LogLevel are:

alert
crit
debug
emerg
error
info
notice
warn

Should I change it to something else?

But it will help if you give an example of the rule in action, and show what happens. At some point there must be an [L]; otherwise what happens at the end with that now-extensionless /blahblah request?

@lucy24, I'm not 100% sure where the problem is coming in, and you obviously don't want me to post 180 lines! LOL But I'm 90% sure that when I had commented everything out except for this section, it had the same infinite loop.

Removing the first rule and changing all of the other rules back to RewriteRule ^blah/?$ fixed the loop.

## Remove trailing /
# tried with [L], too, and it still had an infinite loop
RewriteRule ^(.+)/$ /$1

## Classifieds
RewriteRule ^classifieds/[\w-]+?/(\d+)$ /classifieds/view/?id=$1 [QSA,L]

RewriteRule ^(?:classifieds/)?autos$ /classifieds/list.php?cat=autos [QSA,NC,L]
RewriteRule ^(?:classifieds/)?guns$ /classifieds/list.php?cat=guns-hunting [NC,QSA,L]
RewriteRule ^(?:classifieds/)?pets$ /classifieds/list.php?cat=pets-animals [NC,QSA,L]
RewriteRule ^(?:classifieds/)?dogs$ /classifieds/list.php?cat=pets-animals&subcat=dogs [NC,QSA,NE,L]
RewriteRule ^(?:classifieds/)?yardsales$ /classifieds/list.php?cat=yard-sales [NC,QSA,L]

RewriteCond %{REQUEST_URI} !^/classifieds/(thumbs|photos|reviews|yardsales|list|view|includes) [NC]
RewriteRule ^classifieds/([a-z-]+)/?([a-z-]*)$ /classifieds/list.php?cat=$1&subcat=$2 [NC,QSA,NE,L]

lucy24

7:10 pm on Apr 16, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I changed LogLevel to "alert", and that told me about code errors (like once when I left out a ])
But, but, but--that's the second-highest level. (Apache docs [httpd.apache.org] list an “emerg” even higher than the list you have.) Shouldn't you be setting it to “debug” or even something in the “trace” range? Also try it with and without specifying mod_rewrite, to make double-sure that that’s where the problem is happening:
Specifying a level without a module name will reset the level for all modules to that level. Specifying a level with a module name will set the level for that module only.


Now, then, the troublemaker:
RewriteRule ^(.+)/$ /$1
... and then what happens? At what point does the request get converted back into something that yields a physical file? That’s why I asked if these are real, physical directories. If they were--which I hope they aren’t--the request would meet mod_dir and things would keep going around in circles forever.

w3dk

9:50 pm on Apr 16, 2020 (gmt 0)

10+ Year Member Top Contributors Of The Month



My options for LogLevel are:

alert
crit
debug
emerg
error
info
notice
warn


Is that how WHM is presenting the available options - in alphabetical order (not helpful at all)?! They should be listed in order of severity, as in the Apache docs (that lucy24 links to above), otherwise you don't really know what to set it to. (?)

(@lucy24's post above [edit: and the URL below].... The WebmasterWorld forum seems to be stripping the fragment identifier from the link as it passes through the "link-script"?!)

[httpd.apache.org...]

lucy24

10:54 pm on Apr 16, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Yup, the Forums’ propensity for stripping away fragments is well-attested. But it’s still there if you do whatever your browser and/or OS require for “copy link”:
http://httpd.apache.org/docs/2.4/mod/core.html#loglevel


And oops, I didn't even notice that the posted list is in alphabetical order--which Apache's list certainly isn’t. (Although it sure would be useful if they could have come up with names in alphabetical order! Some book series do this, so when they’re shelved at the library you don’t have to figure out which one comes next in the chronology.)

csdude55

12:55 am on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Is that how WHM is presenting the available options - in alphabetical order (not helpful at all)?! They should be listed in order of severity, as in the Apache docs (that lucy24 links to above), otherwise you don't really know what to set it to. (?)

Yup, that's exactly how the list is in WHM. "trace" doesn't exist, and there's no option to set a module or anything.

I chose "alert" because someone earlier had suggested LogLevel alert rewrite:trace3 (or something similar). I can't set that in .htaccess, and in WHM this was the closest I had. I'd tried reading the docs, but honestly found their explanation to be even more confusing! LOL

I'll change it to "info" tonight and see how it goes.

... and then what happens? At what point does the request get converted back into something that yields a physical file? That’s why I asked if these are real, physical directories. If they were--which I hope they aren’t--the request would meet mod_dir and things would keep going around in circles forever.

@lucy24, maybe I'm misunderstanding your question... my .htaccess has those 9 lines exactly like that, in that order, and I was getting an infinite loop on every page. I made a point to check on all of the sections under classifieds, all of which should have matched and then threw the [L].

So assuming, for example, the user went to example.com/classifieds/foo/12345/. My theory was that it would first hit the line to remove the trailing /, so now it has:

classifieds/foo/12345

Then it would hit the second rule:

RewriteRule ^classifieds/[\w-]+?/(\d+)$ /classifieds/view/?id=$1 [QSA,L]


where it would be rewritten to classifieds/view/?id=12345. Or, more exact, it would go to classifieds/view/index.php?id=12345, because index.php is implicit. And that rule has [L], so that's where it would end.

My logic is obviously off, though, because it's getting in a loop. I just don't know where I'm wrong.

lucy24

4:00 am on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



maybe I'm misunderstanding your question.
And who could blame you, since I was having a severe brain fart and failed to see what you were doing in all those RewriteRules. My bad.

Anyway, I think I’m starting to see the problem.
more exact, it would go to classifieds/view/index.php?id=12345, because index.php is implicit.
index.php (or index.anything) is not “implicit”. It is stated in your DirectoryIndex directive, which tells mod_dir what physical file to go looking for when there is a request for, let's say,
/classifieds/view/?id=blahblah

After mod_dir does its thing, the new request moves through all the modules again, from top to bottom, this time applying any and all RewriteRules to
/classifieds/view/index.php?id=blahblah

But why, why, why? You're doing internal rewrites. Nobody but your server can see the targets. That means you should be spelling out the actual name of the actual document that is actually meant to be deployed.

csdude55

6:10 am on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I honestly had never given that a second thought, @lucy24! I never considered that it was having to loop through to fix that, so I went through and added index.php where appropriate.

I changed my LogLevel to "info", then reinstated the line to remove the trailing /. I still get the infinite loop, though, and no error for it in the log file. I'm getting a ton of warnings from obvious bots trying to find files that don't exist, though, so it's definitely working! LOL And it looks like I'm gonna have a few more files to add to my "forbidden" list... but I digress.

This time I realized that I was getting the infinite loop on a page that exists, though, and has no rules in the .htaccess... so I checked one that DOES rewrite, and it worked just fine! So this is a new issue, harking back to what you (lucy) said earlier about real directories.

I changed it to this:

RewriteCond %{REQUEST_URI} !-f
RewriteCond %{REQUEST_URI}/index\.php !-f
RewriteCond %{REQUEST_URI} !-d
RewriteRule ^(.+)/$ /$1

if the request isn't a file, and the request + /index.php isn't a file, and the request isn't a directory, remove the trailing /

But I go to example.com/classifieds/, which does exist as a directory that rewrites (is that the right word here?) to /classifieds/index.php, and I'm still getting the infinite loop.

lucy24

4:29 pm on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



RewriteCond %{REQUEST_URI}/index\.php !-f
Wouldn’t that make two // slashes? And aren’t the last two lines redundant? If it contains an /index.php file, then by definition it would have to be a real directory. (And, vice versa, every directory would contain an index.php file, unless you need to exclude things like /images/ or perhaps /stylesheets/ depending-on-site.) Contrariwise, the first line is unnecessary, because real physical filenames do not end in / slash.

In any case I hope those -f and -d tests are just for testing purposes, as they're horribly inefficient: the server has to go physically looking for the file every single time.

I'm getting a ton of warnings from obvious bots
wtf? I thought this was an under-construction site, currently not accessible to anyone except yourself. (And possibly to things like link checkers that you’ve individually authorized.)

I still get the infinite loop, though, and no error for it in the log file.
“It isn’t a bug, it’s a feature.” The server is doing precisely what you’ve told it to do, succeeding at every step of the way, so no errors. You do need to look more closely at the RewriteLog--which I realize goes by a different name in 2.4, but someone else will have to help out on details--because that’s where you see if things are getting rewritten the way they’re supposed to, and only the way they’re supposed to.

It kinda sounds as if you are doing all this by some means other than directly editing your config file (keep a copy! ! !) in a text editor. Is that right?

csdude55

6:59 pm on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Wouldn’t that make two // slashes? And aren’t the last two lines redundant? If it contains an /index.php file, then by definition it would have to be a real directory. (And, vice versa, every directory would contain an index.php file, unless you need to exclude things like /images/ or perhaps /stylesheets/ depending-on-site.) Contrariwise, the first line is unnecessary, because real physical filenames do not end in / slash.

I kinda thought the same thing, but I wasn't sure how REQUEST_URI would handle it if the user typed in example.com/classifieds (without the trailing backslash). The server automatically redirects to example.com/classifieds/ , but I don't know where that happens so I wasn't sure if it was before or after this.

In any case I hope those -f and -d tests are just for testing purposes, as they're horribly inefficient: the server has to go physically looking for the file every single time.

Exactly, I just plugged it in so I could pinpoint the problem. But it didn't help, soo...

wtf? I thought this was an under-construction site, currently not accessible to anyone except yourself. (And possibly to things like link checkers that you’ve individually authorized.)

It is, but it's a /ww2/ subdomain on a live site. Changing LogLevel via WHM changed it server-wide, so (as far as I know) the error log for my /ww2/ subdomain is mashed together with the errors from the live domain.

You do need to look more closely at the RewriteLog--which I realize goes by a different name in 2.4, but someone else will have to help out on details--because that’s where you see if things are getting rewritten the way they’re supposed to, and only the way they’re supposed to.

Unfortunately, it looks like 2.4 started merging the rewrite log with the general error log:

[cwiki.apache.org...]

Which I absolutely hate, but I haven't found a way around it :-(

It kinda sounds as if you are doing all this by some means other than directly editing your config file (keep a copy!) in a text editor. Is that right?

Yes, right now I'm using the .htaccess in the /ww2/ subdomain directory. Once it's all finished and ready to go live I plan to move everything to a new server, and then I'll put it in the config file.

csdude55

8:22 pm on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



then by definition it would have to be a real directory


@lucy24, any thoughts on why this still matches, when it's a real directory?

RewriteCond %{REQUEST_URI} !-d
RewriteRule ^(.+)/$ /$1


I'm going to example.com/classifieds, which is a real directory at /classifieds/index.php. It immediately redirects to example.com/classifieds/ (with the slash), then gives an ERR_TOO_MANY_REDIRECTS error.

I also tried going to example.com/classifieds/?z=1 (with a trailing slash, and a fake param to make sure it's not a cache problem), and had the same error.

If I go to example.com/classifieds/index.php, there's no error.

I discovered that if I hard-code every real directory that has an index.php then I don't get the loop:

RewriteCond %{REQUEST_URI} !^/(?:foo|bar)/$ [NC]
RewriteRule ^(.+)/$ /$1

But that's not terribly efficient, I had to add 23 directories. And of course, if I add a new directory somewhere then I'll have to remember to modify this.

phranque

8:34 pm on Apr 17, 2020 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



is DirectorySlash On or Off in your config?

lucy24

8:40 pm on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



The server automatically redirects to example.com/classifieds/ , but I don't know where that happens
That’s mod_dir again, doing its other job: If there is a slashless request for the real, physical
/directory
then mod_dir redirects--EXTERNALLY, meaning the visitor has to make a fresh request--to
/directory/
You can easily see this in logs if you intentionally request some real directory and leave off the final slash: there will be a "GET /directory" with 301 response, followed by a "GET /directory/" like that.

then gives an ERR_TOO_MANY_REDIRECTS error.
Who gives the error? The browser, or the server? Does it show up in logs as a long series of requests each getting a 301 response, or as a single request getting a 500 response?

It is sometimes useful for testing purposes to express a rewrite as an external redirect, because then you can readily see--both in access logs and your browser's address bar--exactly what is being requested at every point.

On the broader scale: I think it might be appropriate from here on to select some individual URL at random, work out exactly what we want to have happen with it--user requests A, server provides content from B--and then once it's hammered into shape we can expand it to a generic pattern.

Edit:
It is, but it's a /ww2/ subdomain on a live site. Changing LogLevel via WHM changed it server-wide, so (as far as I know) the error log for my /ww2/ subdomain is mashed together with the errors from the live domain.
What is WHM, anyway? Did you explain it at some point, or is it something I ought to know in any case?

Although you cannot set LogLevel in htaccess, you can set it within a directory section (I just re-checked Apache docs). Doesn’t your ww2 subdomain live in its own physical directory? Seems like it would have to. Does it also have a separate <VirtualHost>? If so, you would be able to designate a separate ErrorLog file (permitted in VirtualHost though not in Directory sections) so you don't have to plow through everything just to get information for the area you're working on.

csdude55

9:34 pm on Apr 17, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



is DirectorySlash On or Off in your config?

@phranque, I don't see it in WHM's "Apache Configuration", but I do have Directory "/" Options. This is where I turn on/off ExecCGI, FollowSymLinks, Includes, IncludesNOEXEC, Indexes, MultiViews, and SymLinksIfOwnerMatch, though, so I think that's different.

I'm guessing that WHM turns it on by default, with no option to turn it off.

Who gives the error? The browser, or the server? Does it show up in logs as a long series of requests each getting a 301 response, or as a single request getting a 500 response?

@lucy24, I'm not getting any error in the log file for it, so I'm guessing it's the browser throwing the error. I might be able to change LogLevel to something higher tonight, though, and see if I get more info. Since it restarts Apache I have to wait until late night to do it :-/

It is sometimes useful for testing purposes to express a rewrite as an external redirect, because then you can readily see--both in access logs and your browser's address bar--exactly what is being requested at every point.

On the broader scale: I think it might be appropriate from here on to select some individual URL at random, work out exactly what we want to have happen with it--user requests A, server provides content from B--and then once it's hammered into shape we can expand it to a generic pattern.

Good ideas, I'll see what I can figure out...

What is WHM, anyway? Did you explain it at some point, or is it something I ought to know in any case?

It's cPanel's mother... WHM/cPanel. It stands for "Web Host Manager":

[cpanel.net...]

For the most part, if I change something in Apache configuration manually then it gets rewritten by WHM when they do another update. That's why I stopped modifying it directly, after losing all of my hard work a few times I gave up! It looks like I can modify other text files that are included in httpd.conf, though, I just have to read through tons of confusing documentation to figure it out.

Although you cannot set LogLevel in htaccess, you can set it within a directory section (I just re-checked Apache docs).

Oh, it was my understanding that they were all in error_log now, like it or not! I'll have to do some research on that.

Doesn’t your ww2 subdomain live in its own physical directory? Seems like it would have to. Does it also have a separate <VirtualHost>? If so, you would be able to designate a separate ErrorLog file (permitted in VirtualHost though not in Directory sections) so you don't have to plow through everything just to get information for the area you're working on.

It does; the live site is at /home/example/www, while the subdomain is at /home/example/ww2.example.com.

I'm not sure how to modify that information with include files (without manually modifying httpd.conf), but I'll see if I can figure it out tonight... wish me luck!

csdude55

6:36 am on Apr 18, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Well, one last update... I realized that I was adding an awful lot of code in an attempt to save much less code, so I was defeating my own goal. So I dropped the idea of a rule to remove all trailing /, and just went back to using /?$ on all of the rules.

So one last question that falls under this same umbrella:

But you will be very careful with these [L]-less rules, won't you? ;)


@lucy24 got me to thinking.

Let's say that this is my code (copied from my first post):

RewriteRule ^blah/(foo|bar)/(.*) /blah/$2?topic=$1 [NC,QSA]
RewriteRule ^blah/[^/]+/([0-9]+)/?$ /blah/?id=$1 [NC,QSA,L]


Since I don't have [L] on that first rule, I'd like to have a safety net to make sure it doesn't continue on past what I think are all of the possible combinations. I don't necessarily want it to stop for every request, just for requests that match the first rule but don't match the next 3 or 4 (so a combination that I didn't consider).

Can you guys and gals suggest such a safety net?

My initial thought is to test all of the previous rules for a query parameter (maybe plug in a dummy on those rules if necessary), and then if the param from the first rule exists but none of the others exist then stop:

RewriteRule ^blah/(foo|bar)/(.*) /blah/$2?topic=$1 [NC,QSA]

RewriteRule ^blah/[^/]+/([0-9]+)/?$ /blah/index.php?id=$1 [NC,QSA,L]
RewriteRule ^blah/favorites/?$ /blah/index.php?favorites=1 [NC,QSA,L]
RewriteRule ^blah/([a-z-]+)/?$ /blah/index.php?p=$1 [NC,QSA,L]

# safety net (not tested)
RewriteCond %{QUERY_STRING} (?:^|&)topic=[a-z-]+ [NC]
RewriteCond %{QUERY_STRING} !(?:^|&)(?:id|favorites|p)= [NC]
RewriteRule ^blah(?:/(.+))? - [NC,QSA,L]

Is there a better way?

lucy24

5:16 pm on Apr 18, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



For the most part, if I change something in Apache configuration manually then it gets rewritten by WHM when they do another update.
Criminy. Isn’t there some way to override this? It’s your own damn server; you should be able to tell it to do what you want, and only what you want.

On the [L] issue: I feel safest appending an [L] to everything, except rules that have no effect on the request itself (for example, setting cookies). In order to do this, you need to think about any requests that can potentially match more than one pattern, and then order your rules so each rule covers anything that might also be matched by a following rule.

A simple analogy: Most people have an index redirect somewhere, along the lines of
blahblah/index.html >> blahblah/ [R=301,L,NS]
and also a canonicalization redirect along the lines of
blahblah >> https://www.example.com/blahblah [R=301,L]
and the redirects go in that order. You don’t need to omit the [L] from the index redirect on the off chance that the protocol or hostname are also wrong, because any request ending in index.html is redirected in a way that includes the full protocol-and-hostname, so they will be corrected regardless.

In your case, it looks as if there are only four potential parameters, and they’re all mutually exclusive. The pair you need to look at is:
RewriteRule ^blah/(foo|bar)/(.*) /blah/$2?topic=$1 [NC,QSA]

RewriteRule ^blah/[^/]+/([0-9]+)/?$ /blah/index.php?id=$1 [NC,QSA,L]
because a request (if not rewritten) could potentially match both patterns, say
/blah/foo/2345/
which would lead to either
/blah/2345?topic=foo
or
/blah/index.php?id=2345

You may need to add one or two rules, but once they are all in the correct order, they should all function with the [L] flag.