How to make a rewrite rule of "Redirect Trailing Slashes If Not A Folder."

This shouldn’t ever happen, unless you have a different redir in your Caddyfile somewhere.

The ③ redirect won’t redirect the rewritten URI if the URI ends in a slash (if {rewrite_uri} not_ends_with /).

But the result is that.

my rule like above,only three(2rewrite 1redir)

xxx.com/abc/ddddddd/ was redirected xxx.com/index.php?/abc/ddddddd when php route exists

Ahh, I figured out the problem, I think!

Double check whether you’re being redirected to xxx.com/index.php?/abc/ddddddd/ - I’ve got a feeling you’re actually getting redirected to xxx.com/index.php?/abc/ddddddd instead (note the lack of trailing slash).

Here’s why:

  rewrite {
    regexp ^(.+)/$
    to {1} {1}/ /index.php?{1}
  }

Notice how your regexp capture group excludes the trailing slash? You’re not putting the original URI back on the end, you’re putting it on sans trailing slash, and thus the redirect fires in the event of the fallback rewrite. Change it to:

  rewrite {
    regexp ^(.+)/$
    to {1} {1}/ /index.php?{1}/
  }

And let me know if that solves the issue.

1 Like
rewrite {
    regexp ^(.+)/$
    to {1} {1}/ /index.php?{1}/
}

the issue has not been solved.

/index.php?{1}/ can’t remove trailing slashes, no effect (because of {rewrite_uri} ends_with /, no redir)

I remember, in old version, there was no such problem (rule like these)


Rectify the above execute results.

xxx.com/abc/ddddddd/ was redirected xxx.com/index.php?/abc/ddddddd when php route exists
when rule like these:

①rewrite {
    regexp ^(.+)/$
    to {1} {1}/ /index.php?{1}
}

②rewrite {
    to {uri} /index.php?{uri}
}

③redir 301 {
    if {uri} ends_with /
    if {rewrite_uri} not_ends_with /
    / {rewrite_uri}
}

Perhaps my understanding of your desired behaviour was incorrect.

If you want to strip the trailing slash from the PHP index, and redirect the client, use: /index.php?{1}

If you don’t want to strip the trailing slash from the PHP index, and therefore not redirect the client, use: /index.php?{1}/ OR /index.php?{uri}

If you want to strip the trailing slash from the PHP index, but not redirect the client, use either form above on the rewrite, and add another condition to your redirect: if {rewrite_uri} not_starts_with /index.php?

i want to strip the trailing slash from the PHP index, redirect when {uri} ends_with / and /index.php?{1} exists.

but now, {rewrite_uri} is /index.php?{1}, so redirect to abc.com/index.php?xxxxxxx
my expectation is abc.com/xxxxxxx

This is very confusing… Firstly,

Whether or not /index.php?{1} exists is not a meaningful question. It’s the last rewrite target, it only gets used if the prior rewrite targets fail, in which case it is always used.

Secondly,

This sounds like you want to redirect the client to the PHP index, but…

This kinda makes it sound like you don’t want to redirect the client, you just want to serve the index on that URL.

Which one is the desired behaviour?


At the moment, I’m seeing two different requirements:

  1. If the client requests an existing file, but with a trailing slash, redirect them to remove the slash.
  2. If they don’t request an existing file, (redirect? don’t redirect?) them to the index file.

↑↑ This means that abc.com/xxxxxxx/ should be rewrited to /index.php?{1} (final url is abc.com/xxxxxxx after rewrite & redir, without the /index.php?)

now final url is abc.com/index.php?xxxxxxx after rewrite & redir, has the /index.php?, not my expectation.


like below, abc.com/yyyyyyyyy was rewrited to /index.php?{uri}, final url is still abc.com/yyyyyyyyy

rewrite {
    to {uri} /index.php?{uri}
}

So, these two, then:

  1. If the client requests an existing file, but with a trailing slash, redirect them to remove the slash.
  2. If they don’t request an existing file, serve them to the index file without redirecting.

Lets refactor a bit, then. Try something like this.

# Check for:
# 1. File without trailing slash (like -f)
# 2. Existing directory (like -d)
# Else, fall back to PHP index
rewrite {
  r ^(.+?)/?$
  to {1} {1}/ /index.php?{1}
}

# If we got a request with an unneeded
# trailing slash, redirect the client
# - don't redirect PHP index, though
redir {
  if {uri} ends_with /
  if {rewrite_uri} not_ends_with /
  if {rewrite_uri} not_starts_with /index.php?
  / {rewrite_uri}
}

merge the origin ① and ②

without effect, abc.com/xxxxxxx/'s final url is still abc.com/xxxxxxx/ after rewrite.
no redir,because of if {rewrite_uri} not_starts_with /index.php?.
because the value of {rewrite_uri} is abc.com/index.php?xxxxxxx after rewrite.

Yep, figured there’s no point keeping them separate, Caddy will be running a regex match on every request anyway and it’s simpler this way.

This should only happen if there isn’t a valid file on disk, but there is a valid directory.

Is that not the case?

Yes, that’s by design - you said you don’t want a redirect here? You want this:

my requirement is: [“Romove Trailing Slashes If Not A Folder.”]

only discuss the php index exists. because I already know how to do it in non-php case.

case① : hasn’t trailing slashes:
url abc.com/auth/login should be rewrite to /index.php?/auth/login, final url is still abc.com/auth/login
can via:

rewrite {
    to {uri} /index.php?{uri}
}

it can work.

case② : has trailing slashes:
url abc.com/auth/login/ should be rewrite to /index.php?/auth/login, final url should be abc.com/auth/login (should not be abc.com/index.php?/auth/login). How to do it?

The Caddyfile example I gave in this post satisfies your requirements as best as I can tell. I set it up locally and ran a few test requests against it:

127.0.0.1 - [12/Oct/2018:17:58:19 +1000] "GET /auth/login HTTP/1.1" 404 14 - /index.php?/auth/login

(/auth/login is rewritten to /index.php?/auth/login and not redirected (no 301 response) - it 404s because I haven’t actually made an index file in my test setup)

127.0.0.1 - [12/Oct/2018:17:58:23 +1000] "GET /auth/login/ HTTP/1.1" 404 14 - /index.php?/auth/login

(again, rewritten to /index.php?/auth/login but not redirected - final URL is exactly what the client requested, unchanged)

This is identical behaviour between both requests, trailing slash or no. It does the same thing as to {uri} /index.php?{uri} will do (except that it has the added behaviour of redirecting requests to a file if there’s an extra trailing slash, as requested).

i hope redirect the url from abc.com/auth/login/ to abc.com/auth/login, not to abc.com/index.php?/auth/login (<- current result)

Why redir depends on rewrite?
Why redir can’t use regexp? (can’t use {1})
now i can’t remove the index.php? from {rewrite_uri}


review the apache rule

# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]

not dir, has trailing slashes, redir, stop match below rule

OK, so I think that you want to redirect to remove the trailing slash, THEN serve the PHP index from the redirected location. Lets try a twist on a previous approach…

  rewrite {
    r ^(.+)/$
    to {1}/ {1}
  }

  rewrite {
    to {path} {path}/ /index.php?{uri}
  }

  redir {
    if {uri} ends_with /
    if {rewrite_uri} not_ends_with /
    / {rewrite_uri}
  }

The result:

127.0.0.1 - - [14/Oct/2018:01:17:57 +1000] "GET /stuff/ HTTP/1.1" 301 0 - /stuff
127.0.0.1 - - [14/Oct/2018:01:17:57 +1000] "GET /stuff HTTP/1.1" 404 14 - /index.php?/stuff

(Again the 404 from the index file because I didn’t put one in to test with)

The only difference from much, much, much earlier in the thread is that I reversed the original rewrite targets. Now it first checks for an existing folder, and if that doesn’t exist, falls back to stripping the slash.

Can you support my requiements? I need 301 not 404

Have a look at my previous post.

It 301’s to remove the trailing slash, then serves the PHP index… Which meets your requirements as you’ve communicated them.

The 404 only occurred in my own tests, because I didn’t put a file there to test with. If you had a PHP index, it would be served.

why?

rewrite {
    regexp ^(.+)/$
    to {1}/ {1}
}

rewrite {
    to {uri} /index.php{uri}
}

redir 301 {
    if {uri} ends_with /
    if {rewrite_uri} not_ends_with /
    / {rewrite_uri}
}

↑↑ It’s ok
case①


case②
↓↓ It’s 404

rewrite {
    regexp ^(.+)/$
    to {1} {1}/
}

rewrite {
    to {uri} /index.php{uri}
}

redir 301 {
    if {uri} ends_with /
    if {rewrite_uri} not_ends_with /
    / {rewrite_uri}
}

because, case② {rewrite_uri} is ends_with /? not redir?

When you rewrite with multiple targets, and none of the targets are present on disk, Caddy will end up going with the last target.

For case①, the first rewrite’s fallback has no trailing slash, so the redirect works for a request that would result in 404.

For case②, the first rewrite’s fallback has a trailing slash, so it doesn’t get redirected if the request would result in 404.

The {uri} ends_with / and {rewrite_uri} not_ends_with / are required to ensure the redirect doesn’t operate on every request, only those that are rewritten when the request is not a folder.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.