cmd: Clean up, simplify reverse proxy command; fix some edge cases

Now we take advantage of the address parsing capabilities of the HTTP
caddyfile.
This commit is contained in:
Matthew Holt 2020-04-27 15:53:38 -06:00
parent 9770ce7c9f
commit c11d0e47a3
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5

View file

@ -20,11 +20,11 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/url" "strconv"
"strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
caddycmd "github.com/caddyserver/caddy/v2/cmd" caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
@ -40,10 +40,15 @@ func init() {
A simple but production-ready reverse proxy. Useful for quick deployments, A simple but production-ready reverse proxy. Useful for quick deployments,
demos, and development. demos, and development.
Simply shuttles HTTP traffic from the --from address to the --to address. Simply shuttles HTTP(S) traffic from the --from address to the --to address.
If the --from address has a domain name, Caddy will attempt to serve the Unless otherwise specified in the addresses, the --from address will be
proxy over HTTPS with a certificate. assumed to be HTTPS if a hostname is given, and the --to address will be
assumed to be HTTP.
If the --from address has a host or IP, Caddy will attempt to serve the
proxy over HTTPS with a certificate (unless overridden by the HTTP scheme
or port).
If --change-host-header is set, the Host header on the request will be modified If --change-host-header is set, the Host header on the request will be modified
from its original incoming value to the address of the upstream. (Otherwise, by from its original incoming value to the address of the upstream. (Otherwise, by
@ -51,7 +56,7 @@ default, all incoming headers are passed through unmodified.)
`, `,
Flags: func() *flag.FlagSet { Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("file-server", flag.ExitOnError) fs := flag.NewFlagSet("file-server", flag.ExitOnError)
fs.String("from", "localhost:443", "Address on which to receive traffic") fs.String("from", "localhost", "Address on which to receive traffic")
fs.String("to", "", "Upstream address to which to to proxy traffic") fs.String("to", "", "Upstream address to which to to proxy traffic")
fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream") fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
return fs return fs
@ -64,43 +69,69 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
to := fs.String("to") to := fs.String("to")
changeHost := fs.Bool("change-host-header") changeHost := fs.Bool("change-host-header")
if from == "" { httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
from = "localhost:443" httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if to == "" {
return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required")
} }
// URLs need a scheme in order to parse successfully // set up the downstream address; assume missing information from given parts
if !strings.Contains(from, "://") { fromAddr, err := httpcaddyfile.ParseAddress(from)
from = "http://" + from
}
if !strings.Contains(to, "://") {
to = "http://" + to
}
fromURL, err := url.Parse(from)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("parsing 'from' URL: %v", err) return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid downstream address %s: %v", from, err)
}
if fromAddr.Path != "" {
return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", from)
}
if fromAddr.Scheme == "" {
if fromAddr.Port == httpPort || fromAddr.Host == "" {
fromAddr.Scheme = "http"
} else {
fromAddr.Scheme = "https"
}
}
if fromAddr.Port == "" {
if fromAddr.Scheme == "http" {
fromAddr.Port = httpPort
} else if fromAddr.Scheme == "https" {
fromAddr.Port = httpsPort
} }
toURL, err := url.Parse(to)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("parsing 'to' URL: %v", err)
} }
if toURL.Port() == "" { // set up the upstream address; assume missing information from given parts
toPort := "80" toAddr, err := httpcaddyfile.ParseAddress(to)
if toURL.Scheme == "https" { if err != nil {
toPort = "443" return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", to, err)
} }
toURL.Host = net.JoinHostPort(toURL.Host, toPort) if toAddr.Path != "" {
return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", to)
} }
if toAddr.Scheme == "" {
if toAddr.Port == httpsPort {
toAddr.Scheme = "https"
} else {
toAddr.Scheme = "http"
}
}
if toAddr.Port == "" {
if toAddr.Scheme == "http" {
toAddr.Port = httpPort
} else if toAddr.Scheme == "https" {
toAddr.Port = httpsPort
}
}
// proceed to build the handler and server
ht := HTTPTransport{} ht := HTTPTransport{}
if toURL.Scheme == "https" { if toAddr.Scheme == "https" {
ht.TLS = new(TLSConfig) ht.TLS = new(TLSConfig)
} }
handler := Handler{ handler := Handler{
TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil), TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
Upstreams: UpstreamPool{{Dial: toURL.Host}}, Upstreams: UpstreamPool{{Dial: net.JoinHostPort(toAddr.Host, toAddr.Port)}},
} }
if changeHost { if changeHost {
@ -118,23 +149,17 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", nil), caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", nil),
}, },
} }
urlHost := fromURL.Hostname() if fromAddr.Host != "" {
if urlHost != "" {
route.MatcherSetsRaw = []caddy.ModuleMap{ route.MatcherSetsRaw = []caddy.ModuleMap{
{ {
"host": caddyconfig.JSON(caddyhttp.MatchHost{urlHost}, nil), "host": caddyconfig.JSON(caddyhttp.MatchHost{fromAddr.Host}, nil),
}, },
} }
} }
listen := ":443"
if urlPort := fromURL.Port(); urlPort != "" {
listen = ":" + urlPort
}
server := &caddyhttp.Server{ server := &caddyhttp.Server{
Routes: caddyhttp.RouteList{route}, Routes: caddyhttp.RouteList{route},
Listen: []string{listen}, Listen: []string{":" + fromAddr.Port},
} }
httpApp := caddyhttp.App{ httpApp := caddyhttp.App{
@ -153,7 +178,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} }
fmt.Printf("Caddy 2 proxying from %s to %s\n", fromURL, toURL) fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), toAddr.String())
select {} select {}
} }