summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/reverseproxy/caddyfile.go
diff options
context:
space:
mode:
authorMatt Holt <mholt@users.noreply.github.com>2022-03-06 17:43:39 -0700
committerGitHub <noreply@github.com>2022-03-06 17:43:39 -0700
commitab0455922ae01bde1a7a5b3bf58eb993efc02db7 (patch)
tree6ecfccc2d29d601fab557092545fddb51ba1ebea /modules/caddyhttp/reverseproxy/caddyfile.go
parentc50094fc9d34099efd705700e6d2efa2fa065412 (diff)
reverseproxy: Dynamic upstreams (with SRV and A/AAAA support) (#4470)
* reverseproxy: Begin refactor to enable dynamic upstreams Streamed here: https://www.youtube.com/watch?v=hj7yzXb11jU * Implement SRV and A/AAA upstream sources Also get upstreams at every retry loop iteration instead of just once before the loop. See #4442. * Minor tweaks from review * Limit size of upstreams caches * Add doc notes deprecating LookupSRV * Provision dynamic upstreams Still WIP, preparing to preserve health checker functionality * Rejigger health checks Move active health check results into handler-specific Upstreams. Improve documentation regarding health checks and upstreams. * Deprecation notice * Add Caddyfile support, use `caddy.Duration` * Interface guards * Implement custom resolvers, add resolvers to http transport Caddyfile * SRV: fix Caddyfile `name` inline arg, remove proto condition * Use pointer receiver * Add debug logs Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Diffstat (limited to 'modules/caddyhttp/reverseproxy/caddyfile.go')
-rw-r--r--modules/caddyhttp/reverseproxy/caddyfile.go221
1 files changed, 221 insertions, 0 deletions
diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go
index e127237..f4b1636 100644
--- a/modules/caddyhttp/reverseproxy/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/caddyfile.go
@@ -53,6 +53,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// reverse_proxy [<matcher>] [<upstreams...>] {
// # upstreams
// to <upstreams...>
+// dynamic <name> [...]
//
// # load balancing
// lb_policy <name> [<options...>]
@@ -190,6 +191,25 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}
+ case "dynamic":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.DynamicUpstreams != nil {
+ return d.Err("dynamic upstreams already specified")
+ }
+ dynModule := d.Val()
+ modID := "http.reverse_proxy.upstreams." + dynModule
+ unm, err := caddyfile.UnmarshalModule(d, modID)
+ if err != nil {
+ return err
+ }
+ source, ok := unm.(UpstreamSource)
+ if !ok {
+ return d.Errf("module %s (%T) is not an UpstreamSource", modID, unm)
+ }
+ h.DynamicUpstreamsRaw = caddyconfig.JSONModuleObject(source, "source", dynModule, nil)
+
case "lb_policy":
if !d.NextArg() {
return d.ArgErr()
@@ -749,6 +769,7 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
// dial_fallback_delay <duration>
// response_header_timeout <duration>
// expect_continue_timeout <duration>
+// resolvers <resolvers...>
// tls
// tls_client_auth <automate_name> | <cert_file> <key_file>
// tls_insecure_skip_verify
@@ -839,6 +860,15 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.ExpectContinueTimeout = caddy.Duration(dur)
+ case "resolvers":
+ if h.Resolver == nil {
+ h.Resolver = new(UpstreamResolver)
+ }
+ h.Resolver.Addresses = d.RemainingArgs()
+ if len(h.Resolver.Addresses) == 0 {
+ return d.Errf("must specify at least one resolver address")
+ }
+
case "tls_client_auth":
if h.TLS == nil {
h.TLS = new(TLSConfig)
@@ -989,10 +1019,201 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
+// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
+//
+// dynamic srv [<name>] {
+// service <service>
+// proto <proto>
+// name <name>
+// refresh <interval>
+// resolvers <resolvers...>
+// dial_timeout <timeout>
+// dial_fallback_delay <timeout>
+// }
+//
+func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ args := d.RemainingArgs()
+ if len(args) > 1 {
+ return d.ArgErr()
+ }
+ if len(args) > 0 {
+ u.Name = args[0]
+ }
+
+ for d.NextBlock(0) {
+ switch d.Val() {
+ case "service":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if u.Service != "" {
+ return d.Errf("srv service has already been specified")
+ }
+ u.Service = d.Val()
+
+ case "proto":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if u.Proto != "" {
+ return d.Errf("srv proto has already been specified")
+ }
+ u.Proto = d.Val()
+
+ case "name":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if u.Name != "" {
+ return d.Errf("srv name has already been specified")
+ }
+ u.Name = d.Val()
+
+ case "refresh":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing refresh interval duration: %v", err)
+ }
+ u.Refresh = caddy.Duration(dur)
+
+ case "resolvers":
+ if u.Resolver == nil {
+ u.Resolver = new(UpstreamResolver)
+ }
+ u.Resolver.Addresses = d.RemainingArgs()
+ if len(u.Resolver.Addresses) == 0 {
+ return d.Errf("must specify at least one resolver address")
+ }
+
+ case "dial_timeout":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad timeout value '%s': %v", d.Val(), err)
+ }
+ u.DialTimeout = caddy.Duration(dur)
+
+ case "dial_fallback_delay":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad delay value '%s': %v", d.Val(), err)
+ }
+ u.FallbackDelay = caddy.Duration(dur)
+
+ default:
+ return d.Errf("unrecognized srv option '%s'", d.Val())
+ }
+ }
+ }
+
+ return nil
+}
+
+// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
+//
+// dynamic a [<name> <port] {
+// name <name>
+// port <port>
+// refresh <interval>
+// resolvers <resolvers...>
+// dial_timeout <timeout>
+// dial_fallback_delay <timeout>
+// }
+//
+func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ args := d.RemainingArgs()
+ if len(args) > 2 {
+ return d.ArgErr()
+ }
+ if len(args) > 0 {
+ u.Name = args[0]
+ u.Port = args[1]
+ }
+
+ for d.NextBlock(0) {
+ switch d.Val() {
+ case "name":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if u.Name != "" {
+ return d.Errf("a name has already been specified")
+ }
+ u.Name = d.Val()
+
+ case "port":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if u.Port != "" {
+ return d.Errf("a port has already been specified")
+ }
+ u.Port = d.Val()
+
+ case "refresh":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("parsing refresh interval duration: %v", err)
+ }
+ u.Refresh = caddy.Duration(dur)
+
+ case "resolvers":
+ if u.Resolver == nil {
+ u.Resolver = new(UpstreamResolver)
+ }
+ u.Resolver.Addresses = d.RemainingArgs()
+ if len(u.Resolver.Addresses) == 0 {
+ return d.Errf("must specify at least one resolver address")
+ }
+
+ case "dial_timeout":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad timeout value '%s': %v", d.Val(), err)
+ }
+ u.DialTimeout = caddy.Duration(dur)
+
+ case "dial_fallback_delay":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad delay value '%s': %v", d.Val(), err)
+ }
+ u.FallbackDelay = caddy.Duration(dur)
+
+ default:
+ return d.Errf("unrecognized srv option '%s'", d.Val())
+ }
+ }
+ }
+
+ return nil
+}
+
const matcherPrefix = "@"
// Interface guards
var (
_ caddyfile.Unmarshaler = (*Handler)(nil)
_ caddyfile.Unmarshaler = (*HTTPTransport)(nil)
+ _ caddyfile.Unmarshaler = (*SRVUpstreams)(nil)
+ _ caddyfile.Unmarshaler = (*AUpstreams)(nil)
)