Working on a shadow/mirror plugin, and I have a question about loading handlers

I’d really like for Caddy to have an option for request mirroring, and I read the conversation on Github issues #4211 and #6706. The other day, I decided to start playing with the idea.

I have a simple but limited POC that actually works for my needs. An example Caddyfile looks like:

http://localhost:8080 {
    route /some/path {
        shadow {
            compare_body

            primary {
                respond Hunter1
            }
            shadow {
                respond Hunter2
            }
        }
    }
}

The idea is to essentially make primary and shadow subdirectives work similarly to the route directive; to define a set of directives for a chain of MiddlewareHandlers. My handler multiplexes the request to both of those handlers (Although my POC only loads a single handler for shadow and single handler for primary right now).

But I’m struggling as I try to expand it to be more broadly useful. The biggest challenge right now: So far the best way I’ve found to unmarshal caddyfile config into child modules is to use the caddy.GetModule function. But that means I’m only able to get handler modules where the following are true, at least for Caddyfile support.

  1. The module can be loaded just from the directive string
  2. The module must implement caddyfile.Unmarshaler, or just not need to (unmarshal any values from the caddyfile).

That works well enough for my simple example above, but if I want to mirror a request to a different path…

    route /v1/my/api {
        shadow {
            compare_body
            primary {
                reverse_proxy https://my-host.com
            }
            shadow {
                uri replace /v1/my/api /v2/my/api # This doesn't work :(
                reverse_proxy https://my-host.com
            }
        }
    }

I can’t unmarshal uri which is registered with a somewhat specialized parser function and is just a directive for loading http.handlers.rewrite which has lots of different behavior behind different directives, and doesn’t implement caddyfile.Unmarshaler.

It would be awesome if there was a hypothetical httpcaddyfile.GetRegisteredHandlerUnmarshaler(directive string) function, so I could just parse this handler directive easily. One thing I want to try doing is forking Caddy so I can play around with the idea. But it feels like there are reasons why the registered directives/parsers aren’t exported in any consumable way.

Before I go off the deep end, I was wondering if anyone had thoughts or feedback on my approach.

1 Like

You want httpcaddyfile.ParseSegmentAsSubroute(h) for this. It’s how reverse_proxy can have handle_response, how route and handle work, etc. You basically invoke it at the point where you reach the { } of your block. When your h.Val() has shadow or primary, then you can call httpcaddyfile.ParseSegmentAsSubroute(h) and you get a caddyhttp.MiddlewareHandler you can embed in your struct. See for example caddy/modules/caddyhttp/rewrite/caddyfile.go at 33c88bd2bb543a726274cdf52899edb0639cf5f6 · caddyserver/caddy · GitHub

2 Likes

@francislavoie that’s awesome. I’ll take a look and get back if I have any more dumb questions.