mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
reverse_proxy: Caddyfile integration (and fix blocks in Dispenser)
This commit is contained in:
parent
f6126acf37
commit
50e62d06bc
3 changed files with 545 additions and 10 deletions
|
@ -236,18 +236,13 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
|
|||
for d.NextArg() {
|
||||
tkns = append(tkns, d.Token())
|
||||
}
|
||||
if d.Next() && d.Val() == "{" {
|
||||
tkns = append(tkns, d.Token())
|
||||
for d.NextBlock() {
|
||||
for d.Nested() {
|
||||
tkns = append(tkns, d.Token())
|
||||
d.NextBlock()
|
||||
}
|
||||
for d.NextBlock() {
|
||||
for d.Nested() {
|
||||
tkns = append(tkns, d.Token())
|
||||
d.NextBlock()
|
||||
}
|
||||
tkns = append(tkns, d.Token())
|
||||
} else {
|
||||
d.cursor--
|
||||
}
|
||||
tkns = append(tkns, d.Token())
|
||||
return NewDispenser(tkns)
|
||||
}
|
||||
|
||||
|
|
486
modules/caddyhttp/reverseproxy/caddyfile.go
Normal file
486
modules/caddyhttp/reverseproxy/caddyfile.go
Normal file
|
@ -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)
|
||||
)
|
54
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
Normal file
54
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
Normal file
|
@ -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 a new issue