78
// up or down. Down backends will not be proxied to.
79
HealthChecks *HealthChecks `json:"health_checks,omitempty"`
80
81
- // Upstreams is the list of backends to proxy to.
81
+ // Upstreams is the static list of backends to proxy to.
82
Upstreams UpstreamPool `json:"upstreams,omitempty"`
83
84
+ // A module for retrieving the list of upstreams dynamically. Dynamic
85
+ // upstreams are retrieved at every iteration of the proxy loop for
86
+ // each request (i.e. before every proxy attempt within every request).
87
+ // Active health checks do not work on dynamic upstreams, and passive
88
+ // health checks are only effective on dynamic upstreams if the proxy
89
+ // server is busy enough that concurrent requests to the same backends
90
+ // are continuous. Instead of health checks for dynamic upstreams, it
91
+ // is recommended that the dynamic upstream module only return available
92
+ // backends in the first place.
93
+ DynamicUpstreamsRaw json.RawMessage `json:"dynamic_upstreams,omitempty" caddy:"namespace=http.reverse_proxy.upstreams inline_key=source"`
94
+
95
// Adjusts how often to flush the response buffer. By default,
96
// no periodic flushing is done. A negative value disables
97
// response buffering, and flushes immediately after each
148
// - `{http.reverse_proxy.header.*}` The headers from the response
149
HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"`
150
140
- Transport http.RoundTripper `json:"-"`
141
- CB CircuitBreaker `json:"-"`
151
+ Transport http.RoundTripper `json:"-"`
152
+ CB CircuitBreaker `json:"-"`
153
+ DynamicUpstreams UpstreamSource `json:"-"`
154
155
// Holds the parsed CIDR ranges from TrustedProxies
156
trustedProxies []*net.IPNet
178
h.ctx = ctx
179
h.logger = ctx.Logger(h)
180
169
- // verify SRV compatibility
181
+ // verify SRV compatibility - TODO: LookupSRV deprecated; will be removed
182
for i, v := range h.Upstreams {
183
if v.LookupSRV == "" {
184
continue
213
}
214
h.CB = mod.(CircuitBreaker)
215
}
216
+ if h.DynamicUpstreamsRaw != nil {
217
+ mod, err := ctx.LoadModule(h, "DynamicUpstreamsRaw")
218
+ if err != nil {
219
+ return fmt.Errorf("loading upstream source module: %v", err)
220
+ }
221
+ h.DynamicUpstreams = mod.(UpstreamSource)
222
+ }
223
224
// parse trusted proxy CIDRs ahead of time
225
for _, str := range h.TrustedProxies {
289
}
290
291
// set up upstreams
273
- for _, upstream := range h.Upstreams {
274
- // create or get the host representation for this upstream
275
- var host Host = new(upstreamHost)
276
- existingHost, loaded := hosts.LoadOrStore(upstream.String(), host)
277
- if loaded {
278
- host = existingHost.(Host)
279
- }
280
- upstream.Host = host
281
-
282
- // give it the circuit breaker, if any
283
- upstream.cb = h.CB
284
-
285
- // if the passive health checker has a non-zero UnhealthyRequestCount
286
- // but the upstream has no MaxRequests set (they are the same thing,
287
- // but the passive health checker is a default value for for upstreams
288
- // without MaxRequests), copy the value into this upstream, since the
289
- // value in the upstream (MaxRequests) is what is used during
290
- // availability checks
291
- if h.HealthChecks != nil && h.HealthChecks.Passive != nil {
292
- h.HealthChecks.Passive.logger = h.logger.Named("health_checker.passive")
293
- if h.HealthChecks.Passive.UnhealthyRequestCount > 0 &&
294
- upstream.MaxRequests == 0 {
295
- upstream.MaxRequests = h.HealthChecks.Passive.UnhealthyRequestCount
296
- }
297
- }
298
-
299
- // upstreams need independent access to the passive
300
- // health check policy because passive health checks
301
- // run without access to h.
302
- if h.HealthChecks != nil {
303
- upstream.healthCheckPolicy = h.HealthChecks.Passive
304
- }
292
+ for _, u := range h.Upstreams {
293
+ h.provisionUpstream(u)
294
}
295
296
if h.HealthChecks != nil {
402
repl.Set("http.reverse_proxy.duration", time.Since(start))
403
}()
404
405
+ // in the proxy loop, each iteration is an attempt to proxy the request,
406
+ // and because we may retry some number of times, carry over the error
407
+ // from previous tries because of the nuances of load balancing & retries
408
var proxyErr error
409
for {
418
- // choose an available upstream
419
- upstream := h.LoadBalancing.SelectionPolicy.Select(h.Upstreams, clonedReq, w)
420
- if upstream == nil {
421
- if proxyErr == nil {
422
- proxyErr = fmt.Errorf("no upstreams available")
423
- }
424
- if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, clonedReq) {
425
- break
426
- }
427
- continue
410
+ var done bool
411
+ done, proxyErr = h.proxyLoopIteration(clonedReq, w, proxyErr, start, repl, reqHeader, reqHost, next)
412
+ if done {
413
+ break
414
}
415
+ }
416
+
417
+ if proxyErr != nil {
418
+ return statusError(proxyErr)
419
+ }
420
430
- // the dial address may vary per-request if placeholders are
431
- // used, so perform those replacements here; the resulting
432
- // DialInfo struct should have valid network address syntax
433
- dialInfo, err := upstream.fillDialInfo(clonedReq)
421
+ return nil
422
+}
423
+
424
+// proxyLoopIteration implements an iteration of the proxy loop. Despite the enormous amount of local state
425
+// that has to be passed in, we brought this into its own method so that we could run defer more easily.
426
+// It returns true when the loop is done and should break; false otherwise. The error value returned should
427
+// be assigned to the proxyErr value for the next iteration of the loop (or the error handled after break).
428
+func (h *Handler) proxyLoopIteration(r *http.Request, w http.ResponseWriter, proxyErr error, start time.Time,
429
+ repl *caddy.Replacer, reqHeader http.Header, reqHost string, next caddyhttp.Handler) (bool, error) {
430
+ // get the updated list of upstreams
431
+ upstreams := h.Upstreams
432
+ if h.DynamicUpstreams != nil {
433
+ dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r)
434
if err != nil {
435
- return statusError(fmt.Errorf("making dial info: %v", err))
435
+ h.logger.Error("failed getting dynamic upstreams; falling back to static upstreams", zap.Error(err))
436
+ } else {
437
+ upstreams = dUpstreams
438
+ for _, dUp := range dUpstreams {
439
+ h.provisionUpstream(dUp)
440
+ }
441
+ h.logger.Debug("provisioned dynamic upstreams", zap.Int("count", len(dUpstreams)))
442
+ defer func() {
443
+ // these upstreams are dynamic, so they are only used for this iteration
444
+ // of the proxy loop; be sure to let them go away when we're done with them
445
+ for _, upstream := range dUpstreams {
446
+ _, _ = hosts.Delete(upstream.String())
447
+ }
448
+ }()
449
}
450
+ }
451
438
- // attach to the request information about how to dial the upstream;
439
- // this is necessary because the information cannot be sufficiently
440
- // or satisfactorily represented in a URL
441
- caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)
442
-
443
- // set placeholders with information about this upstream
444
- repl.Set("http.reverse_proxy.upstream.address", dialInfo.String())
445
- repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address)
446
- repl.Set("http.reverse_proxy.upstream.host", dialInfo.Host)
447
- repl.Set("http.reverse_proxy.upstream.port", dialInfo.Port)
448
- repl.Set("http.reverse_proxy.upstream.requests", upstream.Host.NumRequests())
449
- repl.Set("http.reverse_proxy.upstream.max_requests", upstream.MaxRequests)
450
- repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails())
451
-
452
- // mutate request headers according to this upstream;
453
- // because we're in a retry loop, we have to copy
454
- // headers (and the Host value) from the original
455
- // so that each retry is identical to the first
456
- if h.Headers != nil && h.Headers.Request != nil {
457
- clonedReq.Header = make(http.Header)
458
- copyHeader(clonedReq.Header, reqHeader)
459
- clonedReq.Host = reqHost
460
- h.Headers.Request.ApplyToRequest(clonedReq)
452
+ // choose an available upstream
453
+ upstream := h.LoadBalancing.SelectionPolicy.Select(upstreams, r, w)
454
+ if upstream == nil {
455
+ if proxyErr == nil {
456
+ proxyErr = fmt.Errorf("no upstreams available")
457
}
462
-
463
- // proxy the request to that upstream
464
- proxyErr = h.reverseProxy(w, clonedReq, repl, dialInfo, next)
465
- if proxyErr == nil || proxyErr == context.Canceled {
466
- // context.Canceled happens when the downstream client
467
- // cancels the request, which is not our failure
468
- return nil
458
+ if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
459
+ return true, proxyErr
460
}
461
+ return false, proxyErr
462
+ }
463
471
- // if the roundtrip was successful, don't retry the request or
472
- // ding the health status of the upstream (an error can still
473
- // occur after the roundtrip if, for example, a response handler
474
- // after the roundtrip returns an error)
475
- if succ, ok := proxyErr.(roundtripSucceeded); ok {
476
- return succ.error
477
- }
464
+ // the dial address may vary per-request if placeholders are
465
+ // used, so perform those replacements here; the resulting
466
+ // DialInfo struct should have valid network address syntax
467
+ dialInfo, err := upstream.fillDialInfo(r)
468
+ if err != nil {
469
+ return true, fmt.Errorf("making dial info: %v", err)
470
+ }
471
479
- // remember this failure (if enabled)
480
- h.countFailure(upstream)
472
+ h.logger.Debug("selected upstream",
473
+ zap.String("dial", dialInfo.Address),
474
+ zap.Int("total_upstreams", len(upstreams)))
475
482
- // if we've tried long enough, break
483
- if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, clonedReq) {
484
- break
485
- }
476
+ // attach to the request information about how to dial the upstream;
477
+ // this is necessary because the information cannot be sufficiently
478
+ // or satisfactorily represented in a URL
479
+ caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)
480
+
481
+ // set placeholders with information about this upstream
482
+ repl.Set("http.reverse_proxy.upstream.address", dialInfo.String())
483
+ repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address)
484
+ repl.Set("http.reverse_proxy.upstream.host", dialInfo.Host)
485
+ repl.Set("http.reverse_proxy.upstream.port", dialInfo.Port)
486
+ repl.Set("http.reverse_proxy.upstream.requests", upstream.Host.NumRequests())
487
+ repl.Set("http.reverse_proxy.upstream.max_requests", upstream.MaxRequests)
488
+ repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails())
489
+
490
+ // mutate request headers according to this upstream;
491
+ // because we're in a retry loop, we have to copy
492
+ // headers (and the r.Host value) from the original
493
+ // so that each retry is identical to the first
494
+ if h.Headers != nil && h.Headers.Request != nil {
495
+ r.Header = make(http.Header)
496
+ copyHeader(r.Header, reqHeader)
497
+ r.Host = reqHost
498
+ h.Headers.Request.ApplyToRequest(r)
499
}
500
488
- return statusError(proxyErr)
501
+ // proxy the request to that upstream
502
+ proxyErr = h.reverseProxy(w, r, repl, dialInfo, next)
503
+ if proxyErr == nil || proxyErr == context.Canceled {
504
+ // context.Canceled happens when the downstream client
505
+ // cancels the request, which is not our failure
506
+ return true, nil
507
+ }
508
+
509
+ // if the roundtrip was successful, don't retry the request or
510
+ // ding the health status of the upstream (an error can still
511
+ // occur after the roundtrip if, for example, a response handler
512
+ // after the roundtrip returns an error)
513
+ if succ, ok := proxyErr.(roundtripSucceeded); ok {
514
+ return true, succ.error
515
+ }
516
+
517
+ // remember this failure (if enabled)
518
+ h.countFailure(upstream)
519
+
520
+ // if we've tried long enough, break
521
+ if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
522
+ return true, proxyErr
523
+ }
524
+
525
+ return false, proxyErr
526
}
527
528
// prepareRequest clones req so that it can be safely modified without
688
// (This method is mostly the beginning of what was borrowed from the net/http/httputil package in the
689
// Go standard library which was used as the foundation.)
690
func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
654
- _ = di.Upstream.Host.CountRequest(1)
691
+ _ = di.Upstream.Host.countRequest(1)
692
//nolint:errcheck
656
- defer di.Upstream.Host.CountRequest(-1)
693
+ defer di.Upstream.Host.countRequest(-1)
694
695
// point the request to this upstream
696
h.directRequest(req, di)
942
req.URL.Host = reqHost
943
}
944
945
+func (h Handler) provisionUpstream(upstream *Upstream) {
946
+ // create or get the host representation for this upstream
947
+ upstream.fillHost()
948
+
949
+ // give it the circuit breaker, if any
950
+ upstream.cb = h.CB
951
+
952
+ // if the passive health checker has a non-zero UnhealthyRequestCount
953
+ // but the upstream has no MaxRequests set (they are the same thing,
954
+ // but the passive health checker is a default value for for upstreams
955
+ // without MaxRequests), copy the value into this upstream, since the
956
+ // value in the upstream (MaxRequests) is what is used during
957
+ // availability checks
958
+ if h.HealthChecks != nil && h.HealthChecks.Passive != nil {
959
+ h.HealthChecks.Passive.logger = h.logger.Named("health_checker.passive")
960
+ if h.HealthChecks.Passive.UnhealthyRequestCount > 0 &&
961
+ upstream.MaxRequests == 0 {
962
+ upstream.MaxRequests = h.HealthChecks.Passive.UnhealthyRequestCount
963
+ }
964
+ }
965
+
966
+ // upstreams need independent access to the passive
967
+ // health check policy because passive health checks
968
+ // run without access to h.
969
+ if h.HealthChecks != nil {
970
+ upstream.healthCheckPolicy = h.HealthChecks.Passive
971
+ }
972
+}
973
+
974
// bufferedBody reads originalBody into a buffer, then returns a reader for the buffer.
975
// Always close the return value when done with it, just like if it was the original body!
976
func (h Handler) bufferedBody(originalBody io.ReadCloser) io.ReadCloser {
1151
Select(UpstreamPool, *http.Request, http.ResponseWriter) *Upstream
1152
}
1153
1154
+// UpstreamSource gets the list of upstreams that can be used when
1155
+// proxying a request. Returned upstreams will be load balanced and
1156
+// health-checked. This should be a very fast function -- instant
1157
+// if possible -- and the return value must be as stable as possible.
1158
+// In other words, the list of upstreams should ideally not change much
1159
+// across successive calls. If the list of upstreams changes or the
1160
+// ordering is not stable, load balancing will suffer. This function
1161
+// may be called during each retry, multiple times per request, and as
1162
+// such, needs to be instantaneous. The returned slice will not be
1163
+// modified.
1164
+type UpstreamSource interface {
1165
+ GetUpstreams(*http.Request) ([]*Upstream, error)
1166
+}
1167
+
1168
// Hop-by-hop headers. These are removed when sent to the backend.
1169
// As of RFC 7230, hop-by-hop headers are required to appear in the
1170
// Connection header field. These are the headers defined by the