Matthew Holt
5 years ago
3 changed files with 545 additions and 10 deletions
@ -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) |
||||
|
) |
@ -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 |
||||
|
} |
Loading…
Reference in new issue