func (h Handler) bufferedBody(originalBody io.ReadCloser) io.ReadCloser {
	buf := bufPool.Get().(*bytes.Buffer)
	buf.Reset()
	if h.MaxBufferSize > 0 {
		n, err := io.CopyN(buf, originalBody, h.MaxBufferSize)
		if err != nil || n == h.MaxBufferSize {
			return bodyReadCloser{
				Reader: io.MultiReader(buf, originalBody),
				buf:    buf,
				body:   originalBody,
			}
		}
	} else {
		_, _ = io.Copy(buf, originalBody)
	}
	originalBody.Close()  
	return bodyReadCloser{
		Reader: buf,
		buf:    buf,
	}
}
bufferedBody reads originalBody into a buffer, then returns a reader for the buffer.
Always close the return value when done with it, just like if it was the original body!
package reverseproxy
import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/textproto"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
	"go.uber.org/zap"
	"golang.org/x/net/http/httpguts"
)
func init() {
	caddy.RegisterModule(Handler{})
}
type Handler struct {
	TransportRaw json.RawMessage `json:"transport,omitempty" caddy:"namespace=http.reverse_proxy.transport inline_key=protocol"`
	CBRaw json.RawMessage `json:"circuit_breaker,omitempty" caddy:"namespace=http.reverse_proxy.circuit_breakers inline_key=type"`
	LoadBalancing *LoadBalancing `json:"load_balancing,omitempty"`
	HealthChecks *HealthChecks `json:"health_checks,omitempty"`
	Upstreams UpstreamPool `json:"upstreams,omitempty"`
	FlushInterval caddy.Duration `json:"flush_interval,omitempty"`
	Headers *headers.Handler `json:"headers,omitempty"`
	BufferRequests bool `json:"buffer_requests,omitempty"`
	BufferResponses bool `json:"buffer_responses,omitempty"`
	MaxBufferSize int64 `json:"max_buffer_size,omitempty"`
	HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"`
	Transport http.RoundTripper `json:"-"`
	CB        CircuitBreaker    `json:"-"`
	responseMatchers map[string]caddyhttp.ResponseMatcher
	handleResponseSegments []*caddyfile.Dispenser
	ctx    caddy.Context
	logger *zap.Logger
}
func (Handler) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.handlers.reverse_proxy",
		New: func() caddy.Module { return new(Handler) },
	}
}
func (h *Handler) Provision(ctx caddy.Context) error {
	h.ctx = ctx
	h.logger = ctx.Logger(h)
	for i, v := range h.Upstreams {
		if v.LookupSRV == "" {
			continue
		}
		if h.HealthChecks != nil && h.HealthChecks.Active != nil {
			return fmt.Errorf(`upstream: lookup_srv is incompatible with active health checks: %d: {"dial": %q, "lookup_srv": %q}`, i, v.Dial, v.LookupSRV)
		}
		if v.Dial != "" {
			return fmt.Errorf(`upstream: specifying dial address is incompatible with lookup_srv: %d: {"dial": %q, "lookup_srv": %q}`, i, v.Dial, v.LookupSRV)
		}
	}
	if h.TransportRaw != nil {
		mod, err := ctx.LoadModule(h, "TransportRaw")
		if err != nil {
			return fmt.Errorf("loading transport: %v", err)
		}
		h.Transport = mod.(http.RoundTripper)
	}
	if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
		mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw")
		if err != nil {
			return fmt.Errorf("loading load balancing selection policy: %s", err)
		}
		h.LoadBalancing.SelectionPolicy = mod.(Selector)
	}
	if h.CBRaw != nil {
		mod, err := ctx.LoadModule(h, "CBRaw")
		if err != nil {
			return fmt.Errorf("loading circuit breaker: %s", err)
		}
		h.CB = mod.(CircuitBreaker)
	}
	if h.Headers != nil {
		err := h.Headers.Provision(ctx)
		if err != nil {
			return fmt.Errorf("provisioning embedded headers handler: %v", err)
		}
	}
	if h.Transport == nil {
		t := &HTTPTransport{
			KeepAlive: &KeepAlive{
				ProbeInterval:       caddy.Duration(30 * time.Second),
				IdleConnTimeout:     caddy.Duration(2 * time.Minute),
				MaxIdleConnsPerHost: 32,  
			},
			DialTimeout: caddy.Duration(10 * time.Second),
		}
		err := t.Provision(ctx)
		if err != nil {
			return fmt.Errorf("provisioning default transport: %v", err)
		}
		h.Transport = t
	}
	if h.LoadBalancing == nil {
		h.LoadBalancing = new(LoadBalancing)
	}
	if h.LoadBalancing.SelectionPolicy == nil {
		h.LoadBalancing.SelectionPolicy = RandomSelection{}
	}
	if h.LoadBalancing.TryDuration > 0 && h.LoadBalancing.TryInterval == 0 {
		h.LoadBalancing.TryInterval = caddy.Duration(250 * time.Millisecond)
	}
	lbMatcherSets, err := ctx.LoadModule(h.LoadBalancing, "RetryMatchRaw")
	if err != nil {
		return err
	}
	err = h.LoadBalancing.RetryMatch.FromInterface(lbMatcherSets)
	if err != nil {
		return err
	}
	for _, upstream := range h.Upstreams {
		var host Host = new(upstreamHost)
		existingHost, loaded := hosts.LoadOrStore(upstream.String(), host)
		if loaded {
			host = existingHost.(Host)
		}
		upstream.Host = host
		upstream.cb = h.CB
		if h.HealthChecks != nil && h.HealthChecks.Passive != nil {
			h.HealthChecks.Passive.logger = h.logger.Named("health_checker.passive")
			if h.HealthChecks.Passive.UnhealthyRequestCount > 0 &&
				upstream.MaxRequests == 0 {
				upstream.MaxRequests = h.HealthChecks.Passive.UnhealthyRequestCount
			}
		}
		if h.HealthChecks != nil {
			upstream.healthCheckPolicy = h.HealthChecks.Passive
		}
	}
	if h.HealthChecks != nil {
		if h.HealthChecks.Passive != nil {
			if h.HealthChecks.Passive.FailDuration > 0 && h.HealthChecks.Passive.MaxFails == 0 {
				h.HealthChecks.Passive.MaxFails = 1
			}
		}
		if h.HealthChecks.Active != nil && (h.HealthChecks.Active.Path != "" ||
			h.HealthChecks.Active.URI != "" ||
			h.HealthChecks.Active.Port != 0) {
			h.HealthChecks.Active.logger = h.logger.Named("health_checker.active")
			timeout := time.Duration(h.HealthChecks.Active.Timeout)
			if timeout == 0 {
				timeout = 5 * time.Second
			}
			if h.HealthChecks.Active.Path != "" {
				h.HealthChecks.Active.logger.Warn("the 'path' option is deprecated, please use 'uri' instead!")
			}
			if h.HealthChecks.Active.URI != "" {
				parsedURI, err := url.Parse(h.HealthChecks.Active.URI)
				if err != nil {
					return err
				}
				h.HealthChecks.Active.uri = parsedURI
			}
			h.HealthChecks.Active.httpClient = &http.Client{
				Timeout:   timeout,
				Transport: h.Transport,
			}
			for _, upstream := range h.Upstreams {
				if h.HealthChecks.Active.Port != 0 {
					upstream.activeHealthCheckPort = h.HealthChecks.Active.Port
				}
			}
			if h.HealthChecks.Active.Interval == 0 {
				h.HealthChecks.Active.Interval = caddy.Duration(30 * time.Second)
			}
			if h.HealthChecks.Active.ExpectBody != "" {
				var err error
				h.HealthChecks.Active.bodyRegexp, err = regexp.Compile(h.HealthChecks.Active.ExpectBody)
				if err != nil {
					return fmt.Errorf("expect_body: compiling regular expression: %v", err)
				}
			}
			go h.activeHealthChecker()
		}
	}
	for i, rh := range h.HandleResponse {
		err := rh.Provision(ctx)
		if err != nil {
			return fmt.Errorf("provisioning response handler %d: %v", i, err)
		}
	}
	return nil
}
func (h *Handler) Cleanup() error {
	for _, upstream := range h.Upstreams {
		_, _ = hosts.Delete(upstream.String())
	}
	return nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
	if h.BufferRequests {
		r.Body = h.bufferedBody(r.Body)
	}
	err := h.prepareRequest(r)
	if err != nil {
		return caddyhttp.Error(http.StatusInternalServerError,
			fmt.Errorf("preparing request for upstream round-trip: %v", err))
	}
	reqHost := r.Host
	reqHeader := r.Header
	origURLScheme := r.URL.Scheme
	origURLHost := r.URL.Host
	r.URL.Scheme = ""
	r.URL.Host = ""
	defer func() {
		r.Host = reqHost      
		r.Header = reqHeader  
		r.URL.Scheme = origURLScheme
		r.URL.Host = origURLHost
	}()
	start := time.Now()
	defer func() {
		repl.Set("http.reverse_proxy.duration", time.Since(start))
	}()
	var proxyErr error
	for {
		upstream := h.LoadBalancing.SelectionPolicy.Select(h.Upstreams, r, w)
		if upstream == nil {
			if proxyErr == nil {
				proxyErr = fmt.Errorf("no upstreams available")
			}
			if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
				break
			}
			continue
		}
		dialInfo, err := upstream.fillDialInfo(r)
		if err != nil {
			return statusError(fmt.Errorf("making dial info: %v", err))
		}
		caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)
		repl.Set("http.reverse_proxy.upstream.address", dialInfo.String())
		repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address)
		repl.Set("http.reverse_proxy.upstream.host", dialInfo.Host)
		repl.Set("http.reverse_proxy.upstream.port", dialInfo.Port)
		repl.Set("http.reverse_proxy.upstream.requests", upstream.Host.NumRequests())
		repl.Set("http.reverse_proxy.upstream.max_requests", upstream.MaxRequests)
		repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails())
		if h.Headers != nil && h.Headers.Request != nil {
			r.Header = make(http.Header)
			copyHeader(r.Header, reqHeader)
			r.Host = reqHost
			h.Headers.Request.ApplyToRequest(r)
		}
		proxyErr = h.reverseProxy(w, r, repl, dialInfo, next)
		if proxyErr == nil || proxyErr == context.Canceled {
			return nil
		}
		if succ, ok := proxyErr.(roundtripSucceeded); ok {
			return succ.error
		}
		h.countFailure(upstream)
		if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
			break
		}
	}
	return statusError(proxyErr)
}
func (h Handler) prepareRequest(req *http.Request) error {
	if req.ContentLength == 0 {
		req.Body = nil  
	}
	req.Close = false
	if _, ok := req.Header["User-Agent"]; !ok {
		req.Header.Set("User-Agent", "")
	}
	reqUpType := upgradeType(req.Header)
	removeConnectionHeaders(req.Header)
	for _, h := range hopHeaders {
		if h == "Te" && httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
			req.Header.Set("Te", "trailers")
			continue
		}
		req.Header.Del(h)
	}
	if reqUpType != "" {
		req.Header.Set("Connection", "Upgrade")
		req.Header.Set("Upgrade", reqUpType)
	}
	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		prior, ok := req.Header["X-Forwarded-For"]
		omit := ok && prior == nil  
		if len(prior) > 0 {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		if !omit {
			req.Header.Set("X-Forwarded-For", clientIP)
		}
	}
	prior, ok := req.Header["X-Forwarded-Proto"]
	omit := ok && prior == nil
	if len(prior) == 0 && !omit {
		proto := "https"
		if req.TLS == nil {
			proto = "http"
		}
		req.Header.Set("X-Forwarded-Proto", proto)
	}
	return nil
}
func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
	_ = di.Upstream.Host.CountRequest(1)
	defer di.Upstream.Host.CountRequest(-1)
	h.directRequest(req, di)
	start := time.Now()
	res, err := h.Transport.RoundTrip(req)
	duration := time.Since(start)
	logger := h.logger.With(
		zap.String("upstream", di.Upstream.String()),
		zap.Duration("duration", duration),
		zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}),
	)
	if err != nil {
		logger.Debug("upstream roundtrip", zap.Error(err))
		return err
	}
	logger.Debug("upstream roundtrip",
		zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)),
		zap.Int("status", res.StatusCode))
	repl.Set("http.reverse_proxy.upstream.latency", duration)
	if di.Upstream.cb != nil {
		di.Upstream.cb.RecordMetric(res.StatusCode, duration)
	}
	if h.HealthChecks != nil && h.HealthChecks.Passive != nil {
		for _, badStatus := range h.HealthChecks.Passive.UnhealthyStatus {
			if caddyhttp.StatusCodeMatches(res.StatusCode, badStatus) {
				h.countFailure(di.Upstream)
			}
		}
		if h.HealthChecks.Passive.UnhealthyLatency > 0 &&
			duration >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) {
			h.countFailure(di.Upstream)
		}
	}
	if h.BufferResponses {
		res.Body = h.bufferedBody(res.Body)
	}
	bodyClosed := false
	for i, rh := range h.HandleResponse {
		if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
			continue
		}
		if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
			statusCode, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
			if err != nil {
				return caddyhttp.Error(http.StatusInternalServerError, err)
			}
			if statusCode != 0 {
				res.StatusCode = statusCode
			}
			break
		}
		if len(rh.Routes) == 0 {
			continue
		}
		for field, value := range res.Header {
			repl.Set("http.reverse_proxy.header."+field, strings.Join(value, ","))
		}
		repl.Set("http.reverse_proxy.status_code", res.StatusCode)
		repl.Set("http.reverse_proxy.status_text", res.Status)
		h.logger.Debug("handling response", zap.Int("handler", i))
		routeErr := rh.Routes.Compile(next).ServeHTTP(rw, req)
		res.Body.Close()
		bodyClosed = true
		if routeErr != nil {
			return roundtripSucceeded{routeErr}
		}
	}
	if res.StatusCode == http.StatusSwitchingProtocols {
		h.handleUpgradeResponse(logger, rw, req, res)
		return nil
	}
	removeConnectionHeaders(res.Header)
	for _, h := range hopHeaders {
		res.Header.Del(h)
	}
	if h.Headers != nil && h.Headers.Response != nil {
		if h.Headers.Response.Require == nil ||
			h.Headers.Response.Require.Match(res.StatusCode, res.Header) {
			h.Headers.Response.ApplyTo(res.Header, repl)
		}
	}
	copyHeader(rw.Header(), res.Header)
	announcedTrailers := len(res.Trailer)
	if announcedTrailers > 0 {
		trailerKeys := make([]string, 0, len(res.Trailer))
		for k := range res.Trailer {
			trailerKeys = append(trailerKeys, k)
		}
		rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
	}
	rw.WriteHeader(res.StatusCode)
	if !bodyClosed {
		err = h.copyResponse(rw, res.Body, h.flushInterval(req, res))
		res.Body.Close()  
		if err != nil {
			h.logger.Error("aborting with incomplete response", zap.Error(err))
			return nil
		}
	}
	if len(res.Trailer) > 0 {
		if fl, ok := rw.(http.Flusher); ok {
			fl.Flush()
		}
	}
	repl.Set("http.reverse_proxy.upstream.duration", duration)
	if len(res.Trailer) == announcedTrailers {
		copyHeader(rw.Header(), res.Trailer)
		return nil
	}
	for k, vv := range res.Trailer {
		k = http.TrailerPrefix + k
		for _, v := range vv {
			rw.Header().Add(k, v)
		}
	}
	return nil
}
func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, proxyErr error, req *http.Request) bool {
	if time.Since(start) >= time.Duration(lb.TryDuration) {
		return false
	}
	if _, ok := proxyErr.(DialError); proxyErr != nil && !ok {
		if lb.RetryMatch == nil && req.Method != "GET" {
			return false
		}
		if !lb.RetryMatch.AnyMatch(req) {
			return false
		}
	}
	select {
	case <-time.After(time.Duration(lb.TryInterval)):
		return true
	case <-ctx.Done():
		return false
	}
}
func (h Handler) directRequest(req *http.Request, di DialInfo) {
	reqHost := di.Address
	if (req.URL.Scheme == "http" && di.Port == "80") ||
		(req.URL.Scheme == "https" && di.Port == "443") {
		reqHost = di.Host
	}
	req.URL.Host = reqHost
}