@marcel91200 / caddy Mirror · read-only / commits / bc453fa6
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

reverseproxy: Correct alternate port for active health checks (#3693)

* reverseproxy: construct active health-check transport from scratch (Fixes #3691) * reverseproxy: do upstream health-check on the correct alternative port * reverseproxy: add integration test for health-check on alternative port * reverseproxy: put back the custom transport for health-check http client * reverseproxy: cleanup health-check integration test * reverseproxy: fix health-check of unix socket upstreams * reverseproxy: skip unix socket tests on Windows * tabs > spaces Co-authored-by: Francis Lavoie <lavofr@gmail.com> * make the linter (and @francislavoie) happy Co-authored-by: Francis Lavoie <lavofr@gmail.com> * One more lint fix Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Mohammed Al Sahaf committed Sep 17, 2020 at 19:25 UTC bc453fa6ae36287c90d2bf6941cb686490090df2
5 files changed +132 -21
caddytest/integration/reverseproxy_test.go new
+97
@@ -0,0 +1,97 @@
1 +package integration
2 +
3 +import (
4 + "fmt"
5 + "io/ioutil"
6 + "net"
7 + "net/http"
8 + "os"
9 + "runtime"
10 + "strings"
11 + "testing"
12 +
13 + "github.com/caddyserver/caddy/v2/caddytest"
14 +)
15 +
16 +func TestReverseProxyHealthCheck(t *testing.T) {
17 + tester := caddytest.NewTester(t)
18 + tester.InitServer(`
19 + {
20 + http_port 9080
21 + https_port 9443
22 + }
23 + http://localhost:2020 {
24 + respond "Hello, World!"
25 + }
26 + http://localhost:2021 {
27 + respond "ok"
28 + }
29 + http://localhost:9080 {
30 + reverse_proxy {
31 + to localhost:2020
32 +
33 + health_path /health
34 + health_port 2021
35 + health_interval 2s
36 + health_timeout 5s
37 + }
38 + }
39 + `, "caddyfile")
40 +
41 + tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
42 +}
43 +
44 +func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
45 + if runtime.GOOS == "windows" {
46 + t.SkipNow()
47 + }
48 + tester := caddytest.NewTester(t)
49 + f, err := ioutil.TempFile("", "*.sock")
50 + if err != nil {
51 + t.Errorf("failed to create TempFile: %s", err)
52 + return
53 + }
54 + // a hack to get a file name within a valid path to use as socket
55 + socketName := f.Name()
56 + os.Remove(f.Name())
57 +
58 + server := http.Server{
59 + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
60 + if strings.HasPrefix(req.URL.Path, "/health") {
61 + w.Write([]byte("ok"))
62 + return
63 + }
64 + w.Write([]byte("Hello, World!"))
65 + }),
66 + }
67 +
68 + unixListener, err := net.Listen("unix", socketName)
69 + if err != nil {
70 + t.Errorf("failed to listen on the socket: %s", err)
71 + return
72 + }
73 + go server.Serve(unixListener)
74 + t.Cleanup(func() {
75 + server.Close()
76 + })
77 + runtime.Gosched() // Allow other goroutines to run
78 +
79 + tester.InitServer(fmt.Sprintf(`
80 + {
81 + http_port 9080
82 + https_port 9443
83 + }
84 + http://localhost:9080 {
85 + reverse_proxy {
86 + to unix/%s
87 +
88 + health_path /health
89 + health_port 2021
90 + health_interval 2s
91 + health_timeout 5s
92 + }
93 + }
94 + `, socketName), "caddyfile")
95 +
96 + tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
97 +}
modules/caddyhttp/reverseproxy/healthchecks.go
+14 -19
@@ -153,32 +153,27 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
153 log.Printf("[PANIC] active health check: %v\n%s", err, debug.Stack())
154 }
155 }()
156 - networkAddr := upstream.Dial
157 - addr, err := caddy.ParseNetworkAddress(networkAddr)
158 - if err != nil {
159 - h.HealthChecks.Active.logger.Error("bad network address",
160 - zap.String("address", networkAddr),
161 - zap.Error(err),
162 - )
163 - return
164 - }
165 - if addr.PortRangeSize() != 1 {
166 - h.HealthChecks.Active.logger.Error("multiple addresses (upstream must map to only one address)",
167 - zap.String("address", networkAddr),
168 - )
169 - return
170 - }
171 - hostAddr := addr.JoinHostPort(0)
172 - if addr.IsUnixNetwork() {
156 +
157 + portStr := strconv.Itoa(upstream.activeHealthCheckPort)
158 + hostAddr := net.JoinHostPort(upstream.networkAddress.Host, portStr)
159 + if upstream.networkAddress.IsUnixNetwork() {
160 // this will be used as the Host portion of a http.Request URL, and
161 // paths to socket files would produce an error when creating URL,
162 // so use a fake Host value instead; unix sockets are usually local
163 hostAddr = "localhost"
164 }
178 - err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: hostAddr}, hostAddr, upstream.Host)
165 +
166 + dialInfo := DialInfo{
167 + Upstream: upstream,
168 + Network: upstream.networkAddress.Network,
169 + Host: upstream.networkAddress.Host,
170 + Port: portStr,
171 + Address: hostAddr,
172 + }
173 + err := h.doActiveHealthCheck(dialInfo, hostAddr, upstream.Host)
174 if err != nil {
175 h.HealthChecks.Active.logger.Error("active health check failed",
181 - zap.String("address", networkAddr),
176 + zap.String("address", hostAddr),
177 zap.Error(err),
178 )
179 }
modules/caddyhttp/reverseproxy/hosts.go
+4 -2
@@ -92,8 +92,10 @@ type Upstream struct {
92 // HeaderAffinity string
93 // IPAffinity string
94
95 - healthCheckPolicy *PassiveHealthChecks
96 - cb CircuitBreaker
95 + networkAddress caddy.NetworkAddress
96 + activeHealthCheckPort int
97 + healthCheckPolicy *PassiveHealthChecks
98 + cb CircuitBreaker
99 }
100
101 func (u Upstream) String() string {
modules/caddyhttp/reverseproxy/httptransport.go
+3
@@ -182,6 +182,9 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
182 if dialInfo, ok := GetDialInfo(ctx); ok {
183 network = dialInfo.Network
184 address = dialInfo.Address
185 + if dialInfo.Upstream.networkAddress.IsUnixNetwork() {
186 + address = dialInfo.Host
187 + }
188 }
189 conn, err := dialer.DialContext(ctx, network, address)
190 if err != nil {
modules/caddyhttp/reverseproxy/reverseproxy.go
+14
@@ -208,9 +208,13 @@ func (h *Handler) Provision(ctx caddy.Context) error {
208 if err != nil {
209 return err
210 }
211 +
212 if addr.PortRangeSize() != 1 {
213 return fmt.Errorf("multiple addresses (upstream must map to only one address): %v", addr)
214 }
215 +
216 + upstream.networkAddress = addr
217 +
218 // create or get the host representation for this upstream
219 var host Host = new(upstreamHost)
220 existingHost, loaded := hosts.LoadOrStore(upstream.String(), host)
@@ -267,6 +271,16 @@ func (h *Handler) Provision(ctx caddy.Context) error {
271 Transport: h.Transport,
272 }
273
274 + for _, upstream := range h.Upstreams {
275 + // if there's an alternative port for health-check provided in the config,
276 + // then use it, otherwise use the port of upstream.
277 + if h.HealthChecks.Active.Port != 0 {
278 + upstream.activeHealthCheckPort = h.HealthChecks.Active.Port
279 + } else {
280 + upstream.activeHealthCheckPort = int(upstream.networkAddress.StartPort)
281 + }
282 + }
283 +
284 if h.HealthChecks.Active.Interval == 0 {
285 h.HealthChecks.Active.Interval = caddy.Duration(30 * time.Second)
286 }