summaryrefslogtreecommitdiff
path: root/modules/caddyhttp
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2019-09-09 12:23:27 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2019-09-09 12:23:27 -0600
commit50e62d06bcbc6b6486b382a22c633772443cfb6d (patch)
treef5ebf9bbbf4d0f4d9098c8db2f67e0b8808f4ffa /modules/caddyhttp
parentf6126acf379963136a6caeb818296a7510abd532 (diff)
reverse_proxy: Caddyfile integration (and fix blocks in Dispenser)
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r--modules/caddyhttp/reverseproxy/caddyfile.go486
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go54
2 files changed, 540 insertions, 0 deletions
diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go
new file mode 100644
index 0000000..ffa3ca0
--- /dev/null
+++ b/modules/caddyhttp/reverseproxy/caddyfile.go
@@ -0,0 +1,486 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package reverseproxy
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
+ "github.com/dustin/go-humanize"
+)
+
+func init() {
+ httpcaddyfile.RegisterHandlerDirective("reverse_proxy", parseCaddyfile)
+}
+
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ rp := new(Handler)
+ err := rp.UnmarshalCaddyfile(h.Dispenser)
+ if err != nil {
+ return nil, err
+ }
+ return rp, nil
+}
+
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// reverse_proxy [<matcher>] [<upstreams...>] {
+// # upstreams
+// to <upstreams...>
+//
+// # load balancing
+// lb_policy <name> [<options...>]
+// lb_try_duration <duration>
+// lb_try_interval <interval>
+//
+// # active health checking
+// health_path <path>
+// health_port <port>
+// health_interval <interval>
+// health_timeout <duration>
+// health_status <status>
+// health_body <regexp>
+//
+// # passive health checking
+// max_fails <num>
+// fail_duration <duration>
+// max_conns <num>
+// unhealthy_status <status>
+// unhealthy_latency <duration>
+//
+// # round trip
+// transport <name> {
+// ...
+// }
+// }
+//
+func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ for _, up := range d.RemainingArgs() {
+ h.Upstreams = append(h.Upstreams, &Upstream{
+ Dial: up,
+ })
+ }
+
+ for d.NextBlock() {
+ switch d.Val() {
+ case "to":
+ args := d.RemainingArgs()
+ if len(args) == 0 {
+ return d.ArgErr()
+ }
+ for _, up := range args {
+ h.Upstreams = append(h.Upstreams, &Upstream{
+ Dial: up,
+ })
+ }
+
+ 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()
+ mod, err := caddy.GetModule("http.handlers.reverse_proxy.selection_policies." + name)
+ if err != nil {
+ return d.Errf("getting load balancing policy module '%s': %v", mod.Name, err)
+ }
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
+ if !ok {
+ return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ }
+ err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
+ if err != nil {
+ return err
+ }
+ sel, ok := unm.(Selector)
+ if !ok {
+ return d.Errf("module %s is not a Selector", mod.Name)
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ h.LoadBalancing.SelectionPolicyRaw = caddyconfig.JSONModuleObject(sel, "policy", name, nil)
+
+ case "lb_try_duration":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.LoadBalancing == nil {
+ h.LoadBalancing = new(LoadBalancing)
+ }
+ dur, err := time.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 := time.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad interval value '%s': %v", d.Val(), err)
+ }
+ h.LoadBalancing.TryInterval = caddy.Duration(dur)
+
+ 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()
+
+ 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_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 := time.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 := time.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[:1])
+ 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 "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 "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 := time.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]
+ }
+ statusNum, err := strconv.Atoi(arg[:1])
+ if err != nil {
+ return d.Errf("bad status value '%s': %v", d.Val(), err)
+ }
+ h.HealthChecks.Passive.UnhealthyStatus = append(h.HealthChecks.Passive.UnhealthyStatus, statusNum)
+ }
+
+ 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 := time.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 "transport":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.TransportRaw != nil {
+ return d.Err("transport already specified")
+ }
+ name := d.Val()
+ mod, err := caddy.GetModule("http.handlers.reverse_proxy.transport." + name)
+ if err != nil {
+ return d.Errf("getting transport module '%s': %v", mod.Name, err)
+ }
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
+ if !ok {
+ return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod.Name)
+ }
+ d.Next() // consume the module name token
+ err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
+ if err != nil {
+ return err
+ }
+ rt, ok := unm.(http.RoundTripper)
+ if !ok {
+ return d.Errf("module %s is not a RoundTripper", mod.Name)
+ }
+ h.TransportRaw = caddyconfig.JSONModuleObject(rt, "protocol", name, nil)
+
+ default:
+ return d.Errf("unrecognized subdirective %s", d.Val())
+ }
+ }
+ }
+
+ return nil
+}
+
+// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
+//
+// transport http {
+// read_buffer <size>
+// write_buffer <size>
+// dial_timeout <duration>
+// tls_client_auth <cert_file> <key_file>
+// tls_insecure_skip_verify
+// tls_timeout <duration>
+// keepalive [off|<duration>]
+// keepalive_idle_conns <max_count>
+// }
+//
+func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.NextBlock() {
+ switch d.Val() {
+ case "read_buffer":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ size, err := humanize.ParseBytes(d.Val())
+ if err != nil {
+ return d.Errf("invalid read buffer size '%s': %v", d.Val(), err)
+ }
+ h.ReadBufferSize = int(size)
+
+ case "write_buffer":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ size, err := humanize.ParseBytes(d.Val())
+ if err != nil {
+ return d.Errf("invalid write buffer size '%s': %v", d.Val(), err)
+ }
+ h.WriteBufferSize = int(size)
+
+ case "dial_timeout":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := time.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad timeout value '%s': %v", d.Val(), err)
+ }
+ h.DialTimeout = caddy.Duration(dur)
+
+ case "tls_client_auth":
+ args := d.RemainingArgs()
+ if len(args) != 2 {
+ return d.ArgErr()
+ }
+ if h.TLS == nil {
+ h.TLS = new(TLSConfig)
+ }
+ h.TLS.ClientCertificateFile = args[0]
+ h.TLS.ClientCertificateKeyFile = args[1]
+
+ case "tls_insecure_skip_verify":
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.TLS == nil {
+ h.TLS = new(TLSConfig)
+ }
+ h.TLS.InsecureSkipVerify = true
+
+ case "tls_timeout":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ dur, err := time.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad timeout value '%s': %v", d.Val(), err)
+ }
+ if h.TLS == nil {
+ h.TLS = new(TLSConfig)
+ }
+ h.TLS.HandshakeTimeout = caddy.Duration(dur)
+
+ case "keepalive":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ if h.KeepAlive == nil {
+ h.KeepAlive = new(KeepAlive)
+ }
+ if d.Val() == "off" {
+ var disable bool
+ h.KeepAlive.Enabled = &disable
+ }
+ dur, err := time.ParseDuration(d.Val())
+ if err != nil {
+ return d.Errf("bad duration value '%s': %v", d.Val(), err)
+ }
+ h.KeepAlive.IdleConnTimeout = caddy.Duration(dur)
+
+ case "keepalive_idle_conns":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ num, err := strconv.Atoi(d.Val())
+ if err != nil {
+ return d.Errf("bad integer value '%s': %v", d.Val(), err)
+ }
+ if h.KeepAlive == nil {
+ h.KeepAlive = new(KeepAlive)
+ }
+ h.KeepAlive.MaxIdleConns = num
+
+ default:
+ return d.Errf("unrecognized subdirective %s", d.Val())
+ }
+ }
+ return nil
+}
+
+// Interface guards
+var (
+ _ caddyfile.Unmarshaler = (*Handler)(nil)
+ _ caddyfile.Unmarshaler = (*HTTPTransport)(nil)
+)
diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
new file mode 100644
index 0000000..c8b9f63
--- /dev/null
+++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
@@ -0,0 +1,54 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fastcgi
+
+import "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+
+// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
+//
+// transport fastcgi {
+// root <path>
+// split <at>
+// env <key> <value>
+// }
+//
+func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.NextBlock() {
+ switch d.Val() {
+ case "root":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ t.Root = d.Val()
+
+ case "split":
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ t.SplitPath = d.Val()
+
+ case "env":
+ args := d.RemainingArgs()
+ if len(args) != 2 {
+ return d.ArgErr()
+ }
+ t.EnvVars = append(t.EnvVars, [2]string{args[0], args[1]})
+
+ default:
+ return d.Errf("unrecognized subdirective %s", d.Val())
+ }
+ }
+ return nil
+}