43
// Handler implements a highly configurable and production-ready reverse proxy.
44
//
45
// Upon proxying, this module sets the following placeholders (which can be used
46
-// both within and after this handler):
46
+// both within and after this handler; for example, in response headers):
47
//
48
// Placeholder | Description
49
// ------------|-------------
54
// `{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream
55
// `{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream
56
// `{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream
57
+// `{http.reverse_proxy.upstream.latency}` | How long it took the proxy upstream to write the response header.
58
+// `{http.reverse_proxy.upstream.duration}` | Time spent proxying to the upstream, including writing response body to client.
59
+// `{http.reverse_proxy.duration}` | Total time spent proxying, including selecting an upstream, retries, and writing response.
60
type Handler struct {
61
// Configures the method of transport for the proxy. A transport
62
// is what performs the actual "round trip" to the backend.
373
}()
374
375
start := time.Now()
376
+ defer func() {
377
+ // total proxying duration, including time spent on LB and retries
378
+ repl.Set("http.reverse_proxy.duration", time.Since(start))
379
+ }()
380
381
var proxyErr error
382
for {
426
}
427
428
// proxy the request to that upstream
422
- proxyErr = h.reverseProxy(w, r, dialInfo, next)
429
+ proxyErr = h.reverseProxy(w, r, repl, dialInfo, next)
430
if proxyErr == nil || proxyErr == context.Canceled {
431
// context.Canceled happens when the downstream client
432
// cancels the request, which is not our failure
529
// reverseProxy performs a round-trip to the given backend and processes the response with the client.
530
// (This method is mostly the beginning of what was borrowed from the net/http/httputil package in the
531
// Go standard library which was used as the foundation.)
525
-func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di DialInfo, next caddyhttp.Handler) error {
532
+func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
533
_ = di.Upstream.Host.CountRequest(1)
534
//nolint:errcheck
535
defer di.Upstream.Host.CountRequest(-1)
554
zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)),
555
zap.Int("status", res.StatusCode))
556
557
+ // duration until upstream wrote response headers (roundtrip duration)
558
+ repl.Set("http.reverse_proxy.upstream.latency", duration)
559
+
560
// update circuit breaker on current conditions
561
if di.Upstream.cb != nil {
562
di.Upstream.cb.RecordMetric(res.StatusCode, duration)
589
continue
590
}
591
582
- repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
583
-
592
// if configured to only change the status code, do that then continue regular proxy response
593
if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
594
statusCode, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
633
if h.Headers != nil && h.Headers.Response != nil {
634
if h.Headers.Response.Require == nil ||
635
h.Headers.Response.Require.Match(res.StatusCode, res.Header) {
628
- repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
636
h.Headers.Response.ApplyTo(res.Header, repl)
637
}
638
}
680
}
681
}
682
683
+ // total duration spent proxying, including writing response body
684
+ repl.Set("http.reverse_proxy.upstream.duration", duration)
685
+
686
if len(res.Trailer) == announcedTrailers {
687
copyHeader(rw.Header(), res.Trailer)
688
return nil