V2 match any path but files

My Caddy version (caddy version): v2.0.0-beta.20

Say I am trying to rewrite

any path starts with /my-spa/ that does not point to a file

to /my-spa/, so that /my-spa/index.html can be served and the real path can be left to the SPA to handle.

I started with this matcher:

@spa {
  path /my-spa/*
  not {
    file {
     try_files {path}
    }
  }
}

It did not seem to work as I expected.

I ended up having it working with:

@spa {
  path /my-spa/*
  not {
    path *.*
  }
}

It’s all good, just wondering if the former was supposed to work and what I did wrong? Cheers.

What was the full config? It’s impossible to know without seeing it, as outside context can influence the matchers.

Also, welcome back!

Hi Matt,

Thanks a lot for the great work on v2.
The full config is not much more than that.

Below is the full config

dev.localhost

log

root * /var/www

file_server

@app {
  path_regexp app ^/(app1|app2|app3)/
  not {
    path *.* # <-- this part
  }
}

rewrite @app /{http.regexp.app.1}/

reverse_proxy /api/* app:4000
reverse_proxy /socket/* app:4000

encode gzip

tls internal

If I replace the marked part in not with what I had before:

file {
  try_files {path}
}

It doesn’t seem to trigger not

I’m trying to reproduce the problem but am so far unable to. Can you provide more details? What is your exact request (give me a specific curl command for instance) and what is the expected result, and what is the actual result (paste its complete output).

Folder structure roughly looks like this under root:

/app1
  /main.js
  /index.html
/app2
  /main.js
  /index.html

and requests would hit

  1. /app1/main.js
  2. /app1/some-scope/some-page

No.1 I’d like it to go through and have the static file served
No.2 I’d like to rewrite to /app1/ so that /app1/index.html gets picked up and served, and the path (/app1/some-scope/some-page) is kept unchanged for the single page app to process

The current behavior is as expected with

@app {
  path_regexp app ^/(app1|app2|app3)/
  not {
    path *.*
  }
}

but serves both url with /app1/index.html with, i.e. not correct when serving /app1/main.js

@app {
  path_regexp app ^/(app1|app2|app3)/
  not {
    file {
      try_files {path}
    }
  }
}

Apologies, apparently I typed the regexp wrong initially. That might have caused some confusion. Fixed in both replies above.

1 Like

Thanks. Can you please provide a curl command and the output you got, vs. the output you expected? “Doesn’t work” isn’t helpful enough.

Edit: Nevermind, for some reason your other post didn’t show up until refresh. Will see what I can do…

Identified it as a bug when the file matcher is used on rewrite redirectives only – quite a niche bug you’ve found yourself, there!

The reason is because the root directive is evaluated after rewrite is, so the site root hasn’t been set when rewrite's matcher is evaluated: Caddyfile Directives — Caddy Documentation

The easy fix is I can move root up so it’s the first directive evaluated… but this means that if the request is rewritten, the root will not change, if it depends on the rewrite.

The thing is, it’s impossible to know which order the user intends, unless they use a route block.

But, I’m willing to bet that rewriting based on the existence of a file is more common than setting the root based on a rewritten URI. So, I can change the standard order.

In the meantime, using a global option of order root before rewrite at the top of your Caddyfile ought to do the trick:

{
	order root before rewrite
}
1 Like

Fixed in httpcaddyfile: Put root directive first, before redir and rewrite · caddyserver/caddy@178ba02 · GitHub. Please try that and report back!

(The CI has build artifacts you can download.)

The order trick works, so I guess the patch should work, too. Thanks for the fix!

1 Like

Also looks like I could just go

not {
   file
}

That’s pretty elegant.

1 Like

I believe that is true! I forgot about that.

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