mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-22 13:59:32 +08:00
reverseproxy: Expand port ranges to multiple upstreams in CLI + Caddyfile (#5494)
* reverseproxy: Expand port ranges to multiple upstreams in CLI + Caddyfile * Add clarifying comment
This commit is contained in:
parent
52d7335c2b
commit
75b690d248
|
@ -0,0 +1,67 @@
|
|||
:8884 {
|
||||
# Port range
|
||||
reverse_proxy localhost:8001-8002
|
||||
|
||||
# Port range with placeholder
|
||||
reverse_proxy {host}:8001-8002
|
||||
|
||||
# Port range with scheme
|
||||
reverse_proxy https://localhost:8001-8002
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:8001"
|
||||
},
|
||||
{
|
||||
"dial": "localhost:8002"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "{http.request.host}:8001"
|
||||
},
|
||||
{
|
||||
"dial": "{http.request.host}:8002"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:8001"
|
||||
},
|
||||
{
|
||||
"dial": "localhost:8002"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,7 +40,29 @@ func parseUpstreamDialAddress(upstreamAddr string) (string, string, error) {
|
|||
|
||||
toURL, err := url.Parse(upstreamAddr)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parsing upstream URL: %v", err)
|
||||
// if the error seems to be due to a port range,
|
||||
// try to replace the port range with a dummy
|
||||
// single port so that url.Parse() will succeed
|
||||
if strings.Contains(err.Error(), "invalid port") && strings.Contains(err.Error(), "-") {
|
||||
index := strings.LastIndex(upstreamAddr, ":")
|
||||
if index == -1 {
|
||||
return "", "", fmt.Errorf("parsing upstream URL: %v", err)
|
||||
}
|
||||
portRange := upstreamAddr[index+1:]
|
||||
if strings.Count(portRange, "-") != 1 {
|
||||
return "", "", fmt.Errorf("parsing upstream URL: parse \"%v\": port range invalid: %v", upstreamAddr, portRange)
|
||||
}
|
||||
toURL, err = url.Parse(strings.ReplaceAll(upstreamAddr, portRange, "0"))
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parsing upstream URL: %v", err)
|
||||
}
|
||||
port = portRange
|
||||
} else {
|
||||
return "", "", fmt.Errorf("parsing upstream URL: %v", err)
|
||||
}
|
||||
}
|
||||
if port == "" {
|
||||
port = toURL.Port()
|
||||
}
|
||||
|
||||
// there is currently no way to perform a URL rewrite between choosing
|
||||
|
@ -51,30 +73,27 @@ func parseUpstreamDialAddress(upstreamAddr string) (string, string, error) {
|
|||
}
|
||||
|
||||
// ensure the port and scheme aren't in conflict
|
||||
urlPort := toURL.Port()
|
||||
if toURL.Scheme == "http" && urlPort == "443" {
|
||||
if toURL.Scheme == "http" && port == "443" {
|
||||
return "", "", fmt.Errorf("upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)")
|
||||
}
|
||||
if toURL.Scheme == "https" && urlPort == "80" {
|
||||
if toURL.Scheme == "https" && port == "80" {
|
||||
return "", "", fmt.Errorf("upstream address has conflicting scheme (https://) and port (:80, the HTTP port)")
|
||||
}
|
||||
if toURL.Scheme == "h2c" && urlPort == "443" {
|
||||
if toURL.Scheme == "h2c" && port == "443" {
|
||||
return "", "", fmt.Errorf("upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)")
|
||||
}
|
||||
|
||||
// if port is missing, attempt to infer from scheme
|
||||
if toURL.Port() == "" {
|
||||
var toPort string
|
||||
if port == "" {
|
||||
switch toURL.Scheme {
|
||||
case "", "http", "h2c":
|
||||
toPort = "80"
|
||||
port = "80"
|
||||
case "https":
|
||||
toPort = "443"
|
||||
port = "443"
|
||||
}
|
||||
toURL.Host = net.JoinHostPort(toURL.Hostname(), toPort)
|
||||
}
|
||||
|
||||
scheme, host, port = toURL.Scheme, toURL.Hostname(), toURL.Port()
|
||||
scheme, host = toURL.Scheme, toURL.Hostname()
|
||||
} else {
|
||||
var err error
|
||||
network, host, port, err = caddy.SplitNetworkAddress(upstreamAddr)
|
||||
|
|
|
@ -149,6 +149,24 @@ func TestParseUpstreamDialAddress(t *testing.T) {
|
|||
expectHostPort: "[::1]:1234",
|
||||
expectScheme: "h2c",
|
||||
},
|
||||
{
|
||||
input: "localhost:1001-1009",
|
||||
expectHostPort: "localhost:1001-1009",
|
||||
},
|
||||
{
|
||||
input: "{host}:1001-1009",
|
||||
expectHostPort: "{host}:1001-1009",
|
||||
},
|
||||
{
|
||||
input: "http://localhost:1001-1009",
|
||||
expectHostPort: "localhost:1001-1009",
|
||||
expectScheme: "http",
|
||||
},
|
||||
{
|
||||
input: "https://localhost:1001-1009",
|
||||
expectHostPort: "localhost:1001-1009",
|
||||
expectScheme: "https",
|
||||
},
|
||||
{
|
||||
input: "unix//var/php.sock",
|
||||
expectHostPort: "unix//var/php.sock",
|
||||
|
@ -196,6 +214,26 @@ func TestParseUpstreamDialAddress(t *testing.T) {
|
|||
input: "http://localhost#fragment",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "http://localhost:8001-8002-8003",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "http://localhost:8001-8002/foo:bar",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "http://localhost:8001-8002/foo:1",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "http://localhost:8001-8002/foo:1-2",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "http://localhost:8001-8002#foo:1",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "http://foo:443",
|
||||
expectErr: true,
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package reverseproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
@ -157,7 +158,25 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
}
|
||||
commonScheme = scheme
|
||||
|
||||
h.Upstreams = append(h.Upstreams, &Upstream{Dial: dialAddr})
|
||||
parsedAddr, err := caddy.ParseNetworkAddress(dialAddr)
|
||||
if err != nil {
|
||||
return d.WrapErr(err)
|
||||
}
|
||||
|
||||
if parsedAddr.StartPort == 0 && parsedAddr.EndPort == 0 {
|
||||
// unix networks don't have ports
|
||||
h.Upstreams = append(h.Upstreams, &Upstream{
|
||||
Dial: dialAddr,
|
||||
})
|
||||
} else {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
@ -153,9 +153,24 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
|
|||
|
||||
upstreamPool := UpstreamPool{}
|
||||
for _, toAddr := range toAddresses {
|
||||
upstreamPool = append(upstreamPool, &Upstream{
|
||||
Dial: toAddr,
|
||||
})
|
||||
parsedAddr, err := caddy.ParseNetworkAddress(toAddr)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toAddr, err)
|
||||
}
|
||||
|
||||
if parsedAddr.StartPort == 0 && parsedAddr.EndPort == 0 {
|
||||
// unix networks don't have ports
|
||||
upstreamPool = append(upstreamPool, &Upstream{
|
||||
Dial: toAddr,
|
||||
})
|
||||
} else {
|
||||
// expand a port range into multiple upstreams
|
||||
for i := parsedAddr.StartPort; i <= parsedAddr.EndPort; i++ {
|
||||
upstreamPool = append(upstreamPool, &Upstream{
|
||||
Dial: caddy.JoinNetworkAddress("", parsedAddr.Host, fmt.Sprint(i)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler := Handler{
|
||||
|
|
Loading…
Reference in New Issue
Block a user