summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/reverseproxy/caddyfile.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp/reverseproxy/caddyfile.go')
-rw-r--r--modules/caddyhttp/reverseproxy/caddyfile.go1107
1 files changed, 603 insertions, 504 deletions
diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go
index 1211188..bcbe744 100644
--- a/modules/caddyhttp/reverseproxy/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/caddyfile.go
@@ -15,12 +15,14 @@
package reverseproxy
import (
- "net"
+ "fmt"
"net/http"
"reflect"
"strconv"
"strings"
+ "github.com/dustin/go-humanize"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@@ -28,7 +30,6 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
- "github.com/dustin/go-humanize"
)
func init() {
@@ -83,10 +84,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// unhealthy_request_count <num>
//
// # streaming
-// flush_interval <duration>
+// flush_interval <duration>
// buffer_requests
// buffer_responses
-// max_buffer_size <size>
+// max_buffer_size <size>
+// stream_timeout <duration>
+// stream_close_delay <duration>
+// trace_logs
//
// # request manipulation
// trusted_proxies [private_ranges] <ranges...>
@@ -142,16 +146,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
h.responseMatchers = make(map[string]caddyhttp.ResponseMatcher)
// appendUpstream creates an upstream for address and adds
- // it to the list. If the address starts with "srv+" it is
- // treated as a SRV-based upstream, and any port will be
- // dropped.
+ // it to the list.
appendUpstream := func(address string) error {
- isSRV := strings.HasPrefix(address, "srv+")
- if isSRV {
- address = strings.TrimPrefix(address, "srv+")
- }
-
- dialAddr, scheme, err := parseUpstreamDialAddress(address)
+ pa, err := parseUpstreamDialAddress(address)
if err != nil {
return d.WrapErr(err)
}
@@ -159,573 +156,641 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// the underlying JSON does not yet support different
// transports (protocols or schemes) to each backend,
// so we remember the last one we see and compare them
- if commonScheme != "" && scheme != commonScheme {
+
+ switch pa.scheme {
+ case "wss":
+ return d.Errf("the scheme wss:// is only supported in browsers; use https:// instead")
+ case "ws":
+ return d.Errf("the scheme ws:// is only supported in browsers; use http:// instead")
+ case "https", "http", "h2c", "":
+ // Do nothing or handle the valid schemes
+ default:
+ return d.Errf("unsupported URL scheme %s://", pa.scheme)
+ }
+
+ if commonScheme != "" && pa.scheme != commonScheme {
return d.Errf("for now, all proxy upstreams must use the same scheme (transport protocol); expecting '%s://' but got '%s://'",
- commonScheme, scheme)
+ commonScheme, pa.scheme)
}
- commonScheme = scheme
+ commonScheme = pa.scheme
- if isSRV {
- if host, _, err := net.SplitHostPort(dialAddr); err == nil {
- dialAddr = host
- }
- h.Upstreams = append(h.Upstreams, &Upstream{LookupSRV: dialAddr})
+ // if the port of upstream address contains a placeholder, only wrap it with the `Upstream` struct,
+ // delaying actual resolution of the address until request time.
+ if pa.replaceablePort() {
+ h.Upstreams = append(h.Upstreams, &Upstream{Dial: pa.dialAddr()})
+ return nil
+ }
+ parsedAddr, err := caddy.ParseNetworkAddress(pa.dialAddr())
+ if err != nil {
+ return d.WrapErr(err)
+ }
+
+ if pa.isUnix() || !pa.rangedPort() {
+ // unix networks don't have ports
+ h.Upstreams = append(h.Upstreams, &Upstream{
+ Dial: pa.dialAddr(),
+ })
} else {
- h.Upstreams = append(h.Upstreams, &Upstream{Dial: dialAddr})
+ // expand a port range into multiple upstreams
+ for i := parsedAddr.StartPort; i <= parsedAddr.EndPort; i++ {
+ h.Upstreams = append(h.Upstreams, &Upstream{
+ Dial: caddy.JoinNetworkAddress("", parsedAddr.Host, fmt.Sprint(i)),
+ })
+ }
}
+
return nil
}
- for d.Next() {
- for _, up := range d.RemainingArgs() {
- err := appendUpstream(up)
+ d.Next() // consume the directive name
+ for _, up := range d.RemainingArgs() {
+ err := appendUpstream(up)
+ if err != nil {
+ return fmt.Errorf("parsing upstream '%s': %w", up, err)
+ }
+ }
+
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ // if the subdirective has an "@" prefix then we
+ // parse it as a response matcher for use with "handle_response"
+ if strings.HasPrefix(d.Val(), matcherPrefix) {
+ err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), h.responseMatchers)
if err != nil {
return err
}
+ continue
}
- for d.NextBlock(0) {
- // if the subdirective has an "@" prefix then we
- // parse it as a response matcher for use with "handle_response"
- if strings.HasPrefix(d.Val(), matcherPrefix) {
- err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), h.responseMatchers)
+ switch d.Val() {
+ case "to":
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return d.ArgErr()
+ }
+ for _, up := range args {
+ err := appendUpstream(up)
if err != nil {
- return err
+ return fmt.Errorf("parsing upstream '%s': %w", up, err)
}
- continue
}
- switch d.Val() {
- case "to":
- args := d.RemainingArgs()
- if len(args) == 0 {
- return d.ArgErr()
- }
- for _, up := range args {
- err := appendUpstream(up)
- if err != nil {
- return err
- }
- }
+ 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 "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()
+ }
+ if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
+ return d.Err("load balancing selection policy already specified")
+ }
+ name := d.Val()
+ modID := "http.reverse_proxy.selection_policies." + name
+ unm, err := caddyfile.UnmarshalModule(d, modID)
+ if err != nil {
+ return err
+ }
+ sel, ok := unm.(Selector)
+ if !ok {
+ return d.Errf("module %s (%T) is not a reverseproxy.Selector", modID, unm)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ h.LoadBalancing.SelectionPolicyRaw = caddyconfig.JSONModuleObject(sel, "policy", name, nil)
- case "lb_policy":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
- return d.Err("load balancing selection policy already specified")
- }
- name := d.Val()
- modID := "http.reverse_proxy.selection_policies." + name
- unm, err := caddyfile.UnmarshalModule(d, modID)
- if err != nil {
- return err
- }
- sel, ok := unm.(Selector)
- if !ok {
- return d.Errf("module %s (%T) is not a reverseproxy.Selector", modID, unm)
- }
- if h.LoadBalancing == nil {
- h.LoadBalancing = new(LoadBalancing)
- }
- h.LoadBalancing.SelectionPolicyRaw = caddyconfig.JSONModuleObject(sel, "policy", name, nil)
+ case "lb_retries":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ tries, err := strconv.Atoi(d.Val())
+ if err != nil {
+ return d.Errf("bad lb_retries number '%s': %v", d.Val(), err)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ h.LoadBalancing.Retries = tries
- case "lb_retries":
- if !d.NextArg() {
- return d.ArgErr()
- }
- tries, err := strconv.Atoi(d.Val())
- if err != nil {
- return d.Errf("bad lb_retries number '%s': %v", d.Val(), err)
- }
- if h.LoadBalancing == nil {
- h.LoadBalancing = new(LoadBalancing)
- }
- h.LoadBalancing.Retries = tries
+ case "lb_try_duration":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad duration value %s: %v", d.Val(), err)
+ }
+ h.LoadBalancing.TryDuration = caddy.Duration(dur)
- case "lb_try_duration":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.LoadBalancing == nil {
- h.LoadBalancing = new(LoadBalancing)
- }
- dur, err := caddy.ParseDuration(d.Val())
- if err != nil {
- return d.Errf("bad duration value %s: %v", d.Val(), err)
- }
- h.LoadBalancing.TryDuration = caddy.Duration(dur)
+ case "lb_try_interval":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad interval value '%s': %v", d.Val(), err)
+ }
+ h.LoadBalancing.TryInterval = caddy.Duration(dur)
- case "lb_try_interval":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.LoadBalancing == nil {
- h.LoadBalancing = new(LoadBalancing)
- }
- dur, err := caddy.ParseDuration(d.Val())
- if err != nil {
- return d.Errf("bad interval value '%s': %v", d.Val(), err)
- }
- h.LoadBalancing.TryInterval = caddy.Duration(dur)
+ case "lb_retry_match":
+ matcherSet, err := caddyhttp.ParseCaddyfileNestedMatcherSet(d)
+ if err != nil {
+ return d.Errf("failed to parse lb_retry_match: %v", err)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ h.LoadBalancing.RetryMatchRaw = append(h.LoadBalancing.RetryMatchRaw, matcherSet)
- case "lb_retry_match":
- matcherSet, err := caddyhttp.ParseCaddyfileNestedMatcherSet(d)
- if err != nil {
- return d.Errf("failed to parse lb_retry_match: %v", err)
- }
- if h.LoadBalancing == nil {
- h.LoadBalancing = new(LoadBalancing)
- }
- h.LoadBalancing.RetryMatchRaw = append(h.LoadBalancing.RetryMatchRaw, matcherSet)
+ case "health_uri":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ h.HealthChecks.Active.URI = d.Val()
- case "health_uri":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- h.HealthChecks.Active.URI = d.Val()
+ case "health_path":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ h.HealthChecks.Active.Path = d.Val()
+ caddy.Log().Named("config.adapter.caddyfile").Warn("the 'health_path' subdirective is deprecated, please use 'health_uri' instead!")
- case "health_path":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- h.HealthChecks.Active.Path = d.Val()
- caddy.Log().Named("config.adapter.caddyfile").Warn("the 'health_path' subdirective is deprecated, please use 'health_uri' instead!")
+ case "health_port":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ portNum, err := strconv.Atoi(d.Val())
+ if err != nil {
+ return d.Errf("bad port number '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Active.Port = portNum
- case "health_port":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
+ case "health_headers":
+ healthHeaders := make(http.Header)
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ key := d.Val()
+ values := d.RemainingArgs()
+ if len(values) == 0 {
+ values = append(values, "")
}
- portNum, err := strconv.Atoi(d.Val())
- if err != nil {
- return d.Errf("bad port number '%s': %v", d.Val(), err)
- }
- h.HealthChecks.Active.Port = portNum
+ healthHeaders[key] = append(healthHeaders[key], values...)
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ h.HealthChecks.Active.Headers = healthHeaders
- case "health_headers":
- healthHeaders := make(http.Header)
- for nesting := d.Nesting(); d.NextBlock(nesting); {
- key := d.Val()
- values := d.RemainingArgs()
- if len(values) == 0 {
- values = append(values, "")
- }
- healthHeaders[key] = values
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- h.HealthChecks.Active.Headers = healthHeaders
+ case "health_interval":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad interval value %s: %v", d.Val(), err)
+ }
+ h.HealthChecks.Active.Interval = caddy.Duration(dur)
- case "health_interval":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- dur, err := caddy.ParseDuration(d.Val())
- if err != nil {
- return d.Errf("bad interval value %s: %v", d.Val(), err)
- }
- h.HealthChecks.Active.Interval = caddy.Duration(dur)
+ case "health_timeout":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad timeout value %s: %v", d.Val(), err)
+ }
+ h.HealthChecks.Active.Timeout = caddy.Duration(dur)
- case "health_timeout":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- dur, err := caddy.ParseDuration(d.Val())
- if err != nil {
- return d.Errf("bad timeout value %s: %v", d.Val(), err)
- }
- h.HealthChecks.Active.Timeout = caddy.Duration(dur)
+ case "health_status":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ val := d.Val()
+ if len(val) == 3 && strings.HasSuffix(val, "xx") {
+ val = val[:1]
+ }
+ statusNum, err := strconv.Atoi(val)
+ if err != nil {
+ return d.Errf("bad status value '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Active.ExpectStatus = statusNum
- case "health_status":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- val := d.Val()
- if len(val) == 3 && strings.HasSuffix(val, "xx") {
- val = val[:1]
- }
- statusNum, err := strconv.Atoi(val)
- if err != nil {
- return d.Errf("bad status value '%s': %v", d.Val(), err)
- }
- h.HealthChecks.Active.ExpectStatus = statusNum
+ case "health_body":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Active == nil {
+ h.HealthChecks.Active = new(ActiveHealthChecks)
+ }
+ h.HealthChecks.Active.ExpectBody = d.Val()
- case "health_body":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Active == nil {
- h.HealthChecks.Active = new(ActiveHealthChecks)
- }
- h.HealthChecks.Active.ExpectBody = d.Val()
+ case "max_fails":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = new(PassiveHealthChecks)
+ }
+ maxFails, err := strconv.Atoi(d.Val())
+ if err != nil {
+ return d.Errf("invalid maximum fail count '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Passive.MaxFails = maxFails
- case "max_fails":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Passive == nil {
- h.HealthChecks.Passive = new(PassiveHealthChecks)
+ case "fail_duration":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = new(PassiveHealthChecks)
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad duration value '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Passive.FailDuration = caddy.Duration(dur)
+
+ case "unhealthy_request_count":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = new(PassiveHealthChecks)
+ }
+ maxConns, err := strconv.Atoi(d.Val())
+ if err != nil {
+ return d.Errf("invalid maximum connection count '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Passive.UnhealthyRequestCount = maxConns
+
+ case "unhealthy_status":
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = new(PassiveHealthChecks)
+ }
+ for _, arg := range args {
+ if len(arg) == 3 && strings.HasSuffix(arg, "xx") {
+ arg = arg[:1]
}
- maxFails, err := strconv.Atoi(d.Val())
+ statusNum, err := strconv.Atoi(arg)
if err != nil {
- return d.Errf("invalid maximum fail count '%s': %v", d.Val(), err)
+ return d.Errf("bad status value '%s': %v", d.Val(), err)
}
- h.HealthChecks.Passive.MaxFails = maxFails
+ h.HealthChecks.Passive.UnhealthyStatus = append(h.HealthChecks.Passive.UnhealthyStatus, statusNum)
+ }
- case "fail_duration":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Passive == nil {
- h.HealthChecks.Passive = new(PassiveHealthChecks)
- }
+ case "unhealthy_latency":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.HealthChecks == nil {
+ h.HealthChecks = new(HealthChecks)
+ }
+ if h.HealthChecks.Passive == nil {
+ h.HealthChecks.Passive = new(PassiveHealthChecks)
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad duration value '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Passive.UnhealthyLatency = caddy.Duration(dur)
+
+ case "flush_interval":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if fi, err := strconv.Atoi(d.Val()); err == nil {
+ h.FlushInterval = caddy.Duration(fi)
+ } else {
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("bad duration value '%s': %v", d.Val(), err)
}
- h.HealthChecks.Passive.FailDuration = caddy.Duration(dur)
+ h.FlushInterval = caddy.Duration(dur)
+ }
- case "unhealthy_request_count":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Passive == nil {
- h.HealthChecks.Passive = new(PassiveHealthChecks)
- }
- maxConns, err := strconv.Atoi(d.Val())
+ case "request_buffers", "response_buffers":
+ subdir := d.Val()
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ val := d.Val()
+ var size int64
+ if val == "unlimited" {
+ size = -1
+ } else {
+ usize, err := humanize.ParseBytes(val)
if err != nil {
- return d.Errf("invalid maximum connection count '%s': %v", d.Val(), err)
+ return d.Errf("invalid byte size '%s': %v", val, err)
}
- h.HealthChecks.Passive.UnhealthyRequestCount = maxConns
+ size = int64(usize)
+ }
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ if subdir == "request_buffers" {
+ h.RequestBuffers = size
+ } else if subdir == "response_buffers" {
+ h.ResponseBuffers = size
+ }
- case "unhealthy_status":
- args := d.RemainingArgs()
- if len(args) == 0 {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Passive == nil {
- h.HealthChecks.Passive = new(PassiveHealthChecks)
- }
- for _, arg := range args {
- if len(arg) == 3 && strings.HasSuffix(arg, "xx") {
- arg = arg[:1]
- }
- statusNum, err := strconv.Atoi(arg)
- if err != nil {
- return d.Errf("bad status value '%s': %v", d.Val(), err)
- }
- h.HealthChecks.Passive.UnhealthyStatus = append(h.HealthChecks.Passive.UnhealthyStatus, statusNum)
- }
+ // TODO: These three properties are deprecated; remove them sometime after v2.6.4
+ case "buffer_requests": // TODO: deprecated
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: buffer_requests: use request_buffers instead (with a maximum buffer size)")
+ h.DeprecatedBufferRequests = true
+ case "buffer_responses": // TODO: deprecated
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: buffer_responses: use response_buffers instead (with a maximum buffer size)")
+ h.DeprecatedBufferResponses = true
+ case "max_buffer_size": // TODO: deprecated
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ size, err := humanize.ParseBytes(d.Val())
+ if err != nil {
+ return d.Errf("invalid byte size '%s': %v", d.Val(), err)
+ }
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: max_buffer_size: use request_buffers and/or response_buffers instead (with maximum buffer sizes)")
+ h.DeprecatedMaxBufferSize = int64(size)
- case "unhealthy_latency":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.HealthChecks == nil {
- h.HealthChecks = new(HealthChecks)
- }
- if h.HealthChecks.Passive == nil {
- h.HealthChecks.Passive = new(PassiveHealthChecks)
- }
+ case "stream_timeout":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if fi, err := strconv.Atoi(d.Val()); err == nil {
+ h.StreamTimeout = caddy.Duration(fi)
+ } else {
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("bad duration value '%s': %v", d.Val(), err)
}
- h.HealthChecks.Passive.UnhealthyLatency = caddy.Duration(dur)
-
- case "flush_interval":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if fi, err := strconv.Atoi(d.Val()); err == nil {
- h.FlushInterval = caddy.Duration(fi)
- } else {
- dur, err := caddy.ParseDuration(d.Val())
- if err != nil {
- return d.Errf("bad duration value '%s': %v", d.Val(), err)
- }
- h.FlushInterval = caddy.Duration(dur)
- }
+ h.StreamTimeout = caddy.Duration(dur)
+ }
- case "request_buffers", "response_buffers":
- subdir := d.Val()
- if !d.NextArg() {
- return d.ArgErr()
- }
- size, err := humanize.ParseBytes(d.Val())
+ case "stream_close_delay":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if fi, err := strconv.Atoi(d.Val()); err == nil {
+ h.StreamCloseDelay = caddy.Duration(fi)
+ } else {
+ dur, err := caddy.ParseDuration(d.Val())
if err != nil {
- return d.Errf("invalid byte size '%s': %v", d.Val(), err)
- }
- if d.NextArg() {
- return d.ArgErr()
- }
- if subdir == "request_buffers" {
- h.RequestBuffers = int64(size)
- } else if subdir == "response_buffers" {
- h.ResponseBuffers = int64(size)
-
+ return d.Errf("bad duration value '%s': %v", d.Val(), err)
}
+ h.StreamCloseDelay = caddy.Duration(dur)
+ }
- // TODO: These three properties are deprecated; remove them sometime after v2.6.4
- case "buffer_requests": // TODO: deprecated
- if d.NextArg() {
- return d.ArgErr()
+ case "trusted_proxies":
+ for d.NextArg() {
+ if d.Val() == "private_ranges" {
+ h.TrustedProxies = append(h.TrustedProxies, caddyhttp.PrivateRangesCIDR()...)
+ continue
}
- caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: buffer_requests: use request_buffers instead (with a maximum buffer size)")
- h.DeprecatedBufferRequests = true
- case "buffer_responses": // TODO: deprecated
- if d.NextArg() {
- return d.ArgErr()
- }
- caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: buffer_responses: use response_buffers instead (with a maximum buffer size)")
- h.DeprecatedBufferResponses = true
- case "max_buffer_size": // TODO: deprecated
- if !d.NextArg() {
- return d.ArgErr()
- }
- size, err := humanize.ParseBytes(d.Val())
- if err != nil {
- return d.Errf("invalid byte size '%s': %v", d.Val(), err)
- }
- if d.NextArg() {
- return d.ArgErr()
- }
- caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: max_buffer_size: use request_buffers and/or response_buffers instead (with maximum buffer sizes)")
- h.DeprecatedMaxBufferSize = int64(size)
+ h.TrustedProxies = append(h.TrustedProxies, d.Val())
+ }
- case "trusted_proxies":
- for d.NextArg() {
- if d.Val() == "private_ranges" {
- h.TrustedProxies = append(h.TrustedProxies, caddyhttp.PrivateRangesCIDR()...)
- continue
- }
- h.TrustedProxies = append(h.TrustedProxies, d.Val())
- }
+ case "header_up":
+ var err error
- case "header_up":
- var err error
+ if h.Headers == nil {
+ h.Headers = new(headers.Handler)
+ }
+ if h.Headers.Request == nil {
+ h.Headers.Request = new(headers.HeaderOps)
+ }
+ args := d.RemainingArgs()
- if h.Headers == nil {
- h.Headers = new(headers.Handler)
+ switch len(args) {
+ case 1:
+ err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], "", "")
+ case 2:
+ // some lint checks, I guess
+ if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
+ caddy.Log().Named("caddyfile").Warn("Unnecessary header_up Host: the reverse proxy's default behavior is to pass headers to the upstream")
}
- if h.Headers.Request == nil {
- h.Headers.Request = new(headers.HeaderOps)
+ if strings.EqualFold(args[0], "x-forwarded-for") && (args[1] == "{remote}" || args[1] == "{http.request.remote}" || args[1] == "{remote_host}" || args[1] == "{http.request.remote.host}") {
+ caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream")
}
- args := d.RemainingArgs()
-
- switch len(args) {
- case 1:
- err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], "", "")
- case 2:
- // some lint checks, I guess
- if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
- caddy.Log().Named("caddyfile").Warn("Unnecessary header_up Host: the reverse proxy's default behavior is to pass headers to the upstream")
- }
- if strings.EqualFold(args[0], "x-forwarded-for") && (args[1] == "{remote}" || args[1] == "{http.request.remote}" || args[1] == "{remote_host}" || args[1] == "{http.request.remote.host}") {
- caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream")
- }
- if strings.EqualFold(args[0], "x-forwarded-proto") && (args[1] == "{scheme}" || args[1] == "{http.request.scheme}") {
- caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream")
- }
- if strings.EqualFold(args[0], "x-forwarded-host") && (args[1] == "{host}" || args[1] == "{http.request.host}" || args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
- caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Host: the reverse proxy's default behavior is to pass headers to the upstream")
- }
- err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], "")
- case 3:
- err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], args[2])
- default:
- return d.ArgErr()
+ if strings.EqualFold(args[0], "x-forwarded-proto") && (args[1] == "{scheme}" || args[1] == "{http.request.scheme}") {
+ caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream")
}
-
- if err != nil {
- return d.Err(err.Error())
+ if strings.EqualFold(args[0], "x-forwarded-host") && (args[1] == "{host}" || args[1] == "{http.request.host}" || args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
+ caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Host: the reverse proxy's default behavior is to pass headers to the upstream")
}
+ err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], "")
+ case 3:
+ err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], args[2])
+ default:
+ return d.ArgErr()
+ }
- case "header_down":
- var err error
+ if err != nil {
+ return d.Err(err.Error())
+ }
- if h.Headers == nil {
- h.Headers = new(headers.Handler)
- }
- if h.Headers.Response == nil {
- h.Headers.Response = &headers.RespHeaderOps{
- HeaderOps: new(headers.HeaderOps),
- }
- }
- args := d.RemainingArgs()
- switch len(args) {
- case 1:
- err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], "", "")
- case 2:
- err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], "")
- case 3:
- err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], args[2])
- default:
- return d.ArgErr()
- }
+ case "header_down":
+ var err error
- if err != nil {
- return d.Err(err.Error())
- }
-
- case "method":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.Rewrite == nil {
- h.Rewrite = &rewrite.Rewrite{}
- }
- h.Rewrite.Method = d.Val()
- if d.NextArg() {
- return d.ArgErr()
+ if h.Headers == nil {
+ h.Headers = new(headers.Handler)
+ }
+ if h.Headers.Response == nil {
+ h.Headers.Response = &headers.RespHeaderOps{
+ HeaderOps: new(headers.HeaderOps),
}
+ }
+ args := d.RemainingArgs()
+ switch len(args) {
+ case 1:
+ err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], "", "")
+ case 2:
+ err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], "")
+ case 3:
+ err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], args[2])
+ default:
+ return d.ArgErr()
+ }
- case "rewrite":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.Rewrite == nil {
- h.Rewrite = &rewrite.Rewrite{}
- }
- h.Rewrite.URI = d.Val()
- if d.NextArg() {
- return d.ArgErr()
- }
+ if err != nil {
+ return d.Err(err.Error())
+ }
- case "transport":
- if !d.NextArg() {
- return d.ArgErr()
- }
- if h.TransportRaw != nil {
- return d.Err("transport already specified")
- }
- transportModuleName = d.Val()
- modID := "http.reverse_proxy.transport." + transportModuleName
- unm, err := caddyfile.UnmarshalModule(d, modID)
- if err != nil {
- return err
- }
- rt, ok := unm.(http.RoundTripper)
- if !ok {
- return d.Errf("module %s (%T) is not a RoundTripper", modID, unm)
- }
- transport = rt
+ case "method":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.Rewrite == nil {
+ h.Rewrite = &rewrite.Rewrite{}
+ }
+ h.Rewrite.Method = d.Val()
+ if d.NextArg() {
+ return d.ArgErr()
+ }
- case "handle_response":
- // delegate the parsing of handle_response to the caller,
- // since we need the httpcaddyfile.Helper to parse subroutes.
- // See h.FinalizeUnmarshalCaddyfile
- h.handleResponseSegments = append(h.handleResponseSegments, d.NewFromNextSegment())
+ case "rewrite":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.Rewrite == nil {
+ h.Rewrite = &rewrite.Rewrite{}
+ }
+ h.Rewrite.URI = d.Val()
+ if d.NextArg() {
+ return d.ArgErr()
+ }
- case "replace_status":
- args := d.RemainingArgs()
- if len(args) != 1 && len(args) != 2 {
- return d.Errf("must have one or two arguments: an optional response matcher, and a status code")
- }
+ case "transport":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.TransportRaw != nil {
+ return d.Err("transport already specified")
+ }
+ transportModuleName = d.Val()
+ modID := "http.reverse_proxy.transport." + transportModuleName
+ unm, err := caddyfile.UnmarshalModule(d, modID)
+ if err != nil {
+ return err
+ }
+ rt, ok := unm.(http.RoundTripper)
+ if !ok {
+ return d.Errf("module %s (%T) is not a RoundTripper", modID, unm)
+ }
+ transport = rt
+
+ case "handle_response":
+ // delegate the parsing of handle_response to the caller,
+ // since we need the httpcaddyfile.Helper to parse subroutes.
+ // See h.FinalizeUnmarshalCaddyfile
+ h.handleResponseSegments = append(h.handleResponseSegments, d.NewFromNextSegment())
+
+ case "replace_status":
+ args := d.RemainingArgs()
+ if len(args) != 1 && len(args) != 2 {
+ return d.Errf("must have one or two arguments: an optional response matcher, and a status code")
+ }
- responseHandler := caddyhttp.ResponseHandler{}
+ responseHandler := caddyhttp.ResponseHandler{}
- if len(args) == 2 {
- if !strings.HasPrefix(args[0], matcherPrefix) {
- return d.Errf("must use a named response matcher, starting with '@'")
- }
- foundMatcher, ok := h.responseMatchers[args[0]]
- if !ok {
- return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
- }
- responseHandler.Match = &foundMatcher
- responseHandler.StatusCode = caddyhttp.WeakString(args[1])
- } else if len(args) == 1 {
- responseHandler.StatusCode = caddyhttp.WeakString(args[0])
+ if len(args) == 2 {
+ if !strings.HasPrefix(args[0], matcherPrefix) {
+ return d.Errf("must use a named response matcher, starting with '@'")
}
-
- // make sure there's no block, cause it doesn't make sense
- if d.NextBlock(1) {
- return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.")
+ foundMatcher, ok := h.responseMatchers[args[0]]
+ if !ok {
+ return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
}
+ responseHandler.Match = &foundMatcher
+ responseHandler.StatusCode = caddyhttp.WeakString(args[1])
+ } else if len(args) == 1 {
+ responseHandler.StatusCode = caddyhttp.WeakString(args[0])
+ }
- h.HandleResponse = append(
- h.HandleResponse,
- responseHandler,
- )
+ // make sure there's no block, cause it doesn't make sense
+ if d.NextBlock(1) {
+ return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.")
+ }
- default:
- return d.Errf("unrecognized subdirective %s", d.Val())
+ h.HandleResponse = append(
+ h.HandleResponse,
+ responseHandler,
+ )
+
+ case "verbose_logs":
+ if h.VerboseLogs {
+ return d.Err("verbose_logs already specified")
}
+ h.VerboseLogs = true
+
+ default:
+ return d.Errf("unrecognized subdirective %s", d.Val())
}
}
@@ -918,6 +983,17 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.MaxResponseHeaderSize = int64(size)
+ case "proxy_protocol":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ switch proxyProtocol := d.Val(); proxyProtocol {
+ case "v1", "v2":
+ h.ProxyProtocol = proxyProtocol
+ default:
+ return d.Errf("invalid proxy protocol version '%s'", proxyProtocol)
+ }
+
case "dial_timeout":
if !d.NextArg() {
return d.ArgErr()
@@ -1324,6 +1400,7 @@ func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// resolvers <resolvers...>
// dial_timeout <timeout>
// dial_fallback_delay <timeout>
+// versions ipv4|ipv6
// }
func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
@@ -1397,8 +1474,30 @@ func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
u.FallbackDelay = caddy.Duration(dur)
+ case "versions":
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return d.Errf("must specify at least one version")
+ }
+
+ if u.Versions == nil {
+ u.Versions = &IPVersions{}
+ }
+
+ trueBool := true
+ for _, arg := range args {
+ switch arg {
+ case "ipv4":
+ u.Versions.IPv4 = &trueBool
+ case "ipv6":
+ u.Versions.IPv6 = &trueBool
+ default:
+ return d.Errf("unsupported version: '%s'", arg)
+ }
+ }
+
default:
- return d.Errf("unrecognized srv option '%s'", d.Val())
+ return d.Errf("unrecognized a option '%s'", d.Val())
}
}
}