V1 to V2: rewrite 404 errors

Hi! I need some help with transforming this V1 directive to V2:

rewrite {
  regexp .*
  to {path} /
}

That snipped is taken from the official Vue docs (HTML5 History Mode | Vue Router), but it is for V1. The aim is to rewrite all requests that would produce a 404 to / (index).

I have searched and tried different things, like this:

:80 {
  encode gzip
  file_server browse {
    root /some/path/web
  }
  reverse_proxy /xy/* 127.0.0.1:10000
  matcher a {
    path_regexp .*
  }
  rewrite match:a /
}

Everything works fine as long as I don’t include the matcher/rewrite part. Then I get error messages like Error during parsing: unknown subdirective 'matcher'.

Simple rewrites like rewrite * / do work, but then of course everything is rewritten, also existing files.

Anybody who can give me a hint? Thanks a lot!

v2 has a special directive to do just this! The try_files directive.

I think that for you, it’ll look like, but YMMV:

try_files {path} /

Also, the matcher syntax has changed a few betas ago, it now looks like this:

@a {
    ...
}
rewrite @a /

Simpler, nicer to read.

2 Likes

Thank you, Francis, for your fast reply!

Unfortunately it does not work. If I use try_files {path} / it has no effect (still returns 404 if path not available).

@a {
  path_regexp .*
}
rewrite @a /

does something, but seems to rewrite everything again, not only those paths that are not found.

Samlple path would be localhost/dashboard which should be redirected to localhost/index.html.

Ah, then might try_files {path} /index.html work?

Had the same idea already and tried it (and again now), but still gives a 404.

What is your full (working) v1 config, and your full v2 config so far? (No redactions or modifications please) - also the exact request and its response, i.e. curl -v ...

We started directly with v2 (just a week ago). I will try if I get v1 to work with the Vue sample code. Good point.

This is the full and unedited config that is working like charm except the try_files line. It is a local development environment (macOS):

:80 {
  encode gzip
  file_server browse {
    root /Users/shared/server/components/web
  }
  reverse_proxy /soc/* 127.0.0.1:9710
  reverse_proxy /api/* 127.0.0.1:9720
  reverse_proxy /fio/* 127.0.0.1:9730
  try_files {path} /index.html
}

This is the request (same in browser and other tools):

> GET /ressources HTTP/1.1
> Host: localhost
> User-Agent: insomnia/7.1.0
> Accept: */*

< HTTP/1.1 404 Not Found
< Server: Caddy
< Date: Fri, 28 Feb 2020 18:09:05 GMT
< Content-Length: 0

Same request without the path is working:

> GET / HTTP/1.1
> Host: localhost
> User-Agent: insomnia/7.1.0
> Accept: */*

< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Length: 1562
< Content-Type: text/html; charset=utf-8
< Etag: "q6ez9z17e"
< Last-Modified: Fri, 28 Feb 2020 13:39:35 GMT
< Server: Caddy
< Date: Fri, 28 Feb 2020 18:12:20 GMT

Ah, move root outside the file_server:

root * /Users/shared/server/components/web
file_server browse

When inside, only the file server knows the site root. When outside, the rest of the directives can know it, too. try_files needs to know where to look for files.

1 Like

Thank you for your help, Matt.

That fixes the problem, but creates a new one: all reverse_proxy directives do not work anymore. Everything below file_server browse seems to be handled via try_files now.

:80 {
  encode gzip
  root * /Users/shared/server/components/web
  file_server browse
  reverse_proxy /soc/* 127.0.0.1:9710
  reverse_proxy /api/* 127.0.0.1:9720
  reverse_proxy /fio/* 127.0.0.1:9730
  try_files {path} /index.html
}

If I remove the try_files line, everything works again except the rewrite.

That’s because you’re telling Caddy to rewrite all requests to index.html if the requested path doesn’t exist as a file on disk. Since index.html does not match any of your paths on your reverse proxy, they don’t evaluate.

You’ll need to decide whether you want to serve static files or reverse proxy and then route requests accordingly.

Here’s my attempt:

:80

encode gzip
root * /Users/shared/server/components/web

handle /soc/* {
	reverse_proxy 127.0.0.1:9710
}
handle /api/* {
	reverse_proxy 127.0.0.1:9720
}
handle /fio/* {
	reverse_proxy 127.0.0.1:9730
}
handle {
	try_files {path} /index.html
	file_server browse
}

I’m just spitballing here… give it a try / derive from it if it needs tweaking.

There might be an even easier and more elegant way.

2 Likes

Thank you, Matt, for your support! This works perfectly - problem solved. Slowly I start getting into the caddyfile logic.

Caddy is such a pleasant and valuable product!

1 Like

Thank you for the kind feedback!

There are probably several ways to accomplish your requirements in the Caddyfile… that was the first that came to my mind. You can read up on the handle directive to learn why that works.

In summary: there are different ways to structure or express routing logic. The traditional, “flat” Caddyfile way is to define behavior together (directives) and have routing be a secondary consideration (matchers). While this makes it easy to express many composable behaviors, it makes it difficult to express complex routing, with lots of “ifs” “ands” or “buts” so to speak. To visualize this, look at one of your later attempts. This is like a “flat” approach, where you just list the directives and matchers within them.

Another way is, I dunno what else to call it, so the “nested” approach. This is the inverse: routing is the primary consideration, and behavior is expressed within those routes. In this paradigm, routing logic is defined on the “outside” and behavior is defined on the “inside”. (In the “flat” paradigm, behavior is expressed on the “outside” and routing on the “inside”.) This is more like how nginx config works, with location blocks. In the Caddyfile, the handle and route directives lend to this method, hence my solution.

Compare your initial config and my proposed solution and you’ll see the structural differences I’m talking about. I moved the routing logic to the “outside” and put the behavior on the “inside”.

The problem is that it’s difficult to express interdependent logic for whatever is on the “inside”. In other words, inside logic can’t be easily expressed if it depends on other inside logic. For example, in the “flat” paradigm, routing is on the inside: it is difficult to express routing logic that is dependent on other routing, like “if, else if, else if, else if, else”; conversely in the “nested” paradigm, it is difficult to express behavior that is dependent on other behaviors, like “do this, then this, then this, then this, and finally this”.

There are pros and cons to both approaches. Both are necessary, depending on what you need to do. In this case, your routing logic is more complex: you want to invoke certain handlers in a “if, else if, else if, else” fashion – so, the elegant solution is to put routing on the outside, hence the “nested” solution.

AFAIK, nginx config only does the “nested” paradigm, and Caddyfile v1 only did the “flat” paradigm. The v2 Caddyfile supports both! Just depends on what you need/want.

I hope that made any sense… I’d like to write up more about this later when it’s not midnight.

2 Likes

Again, thank you very much, Matt! This was a great clarification for me.

About choosing the rights words for both approaches - I think this is grouping:

  • grouped by path: handle /path/* {...}
  • grouped by method: reverse_proxy { /path/* ...}

So that in a Caddy file it is possible to write single flat lines like this:

handle /soc/* reverse_proxy 127.0.0.1:9710
reverse_proxy /soc/* 127.0.0.1:9710

or groups like this:

handle /path/* {
  try_files {path} /index.html
  file_server browse
}

or a mixture of both. That would be the grouping logic.

The other part of it is the syntax. It can be

method source target

or

handle source method target

So Caddy knows two different syntaxes, and knows grouping - but these are separate concerns.

That grouping reminds me of transactions in databases. It is a group of commands that is isolated from other commands.

Maybe this could help I hope … since I’m a total newbie in Caddy, it might be nonsense.

1 Like

No I like that! I wrote a similar thing a little differently here if it helps. I’ll try to codify it better in the docs…

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