Repository: caddyserver/caddy
File: modules/caddyhttp/reverseproxy/reverseproxy.go (Go)

Code before:
		}
		if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
			return true, proxyErr
		}
		return false, proxyErr
	}

	// the dial address may vary per-request if placeholders are
	// used, so perform those replacements here; the resulting
	// DialInfo struct should have valid network address syntax
	dialInfo, err := upstream.fillDialInfo(r)
	if err != nil {
		return true, fmt.Errorf("making dial info: %v", err)
	}

	h.logger.Debug("selected upstream",
		zap.String("dial", dialInfo.Address),
		zap.Int("total_upstreams", len(upstreams)))

	// attach to the request information about how to dial the upstream;
	// this is necessary because the information cannot be sufficiently
	// or satisfactorily represented in a URL
	caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)

	var remoteAddr string
	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
		h.logger.Debug("using xff header for proxy protocol", zap.Any("xff", xff))
		remoteAddr = strings.TrimSpace(strings.Split(xff, ",")[0])
	} else {
		remoteAddr = r.RemoteAddr
	}

	ipStr, portStr, err := net.SplitHostPort(remoteAddr)
	if err != nil { // probably didn't have a port
		ipStr = remoteAddr
		portStr = "0"
	}
	ip := net.ParseIP(ipStr)
	port, _ := strconv.Atoi(portStr)

	proxyProtocolInfo := ProxyProtocolInfo{
		IP:   ip,
		Port: port,
	}
	caddyhttp.SetVar(r.Context(), proxyProtocolInfoVarKey, proxyProtocolInfo)

	// set placeholders with information about this upstream
	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)

Review comment (suggestion):
I think we should wait for [pr](https://github.com/caddyserver/caddy/pull/5104) to be merged. Parsing xff is not good.

Code after:
		if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
			return true, proxyErr
		}
		return false, proxyErr
	}

	// the dial address may vary per-request if placeholders are
	// used, so perform those replacements here; the resulting
	// DialInfo struct should have valid network address syntax
	dialInfo, err := upstream.fillDialInfo(r)
	if err != nil {
		return true, fmt.Errorf("making dial info: %v", err)
	}

	h.logger.Debug("selected upstream",
		zap.String("dial", dialInfo.Address),
		zap.Int("total_upstreams", len(upstreams)))

	// attach to the request information about how to dial the upstream;
	// this is necessary because the information cannot be sufficiently
	// or satisfactorily represented in a URL
	caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)

	// set placeholders with information about this upstream
	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())

	// mutate request headers according to this upstream;
	// because we're in a retry loop, we have to copy
	// headers (and the r.Host value) from the original
	// so that each retry is identical to the first
	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)
	}

	// proxy the request to that upstream
	proxyErr = h.reverseProxy(w, r, origReq, repl, dialInfo, next)
	if proxyErr == nil || errors.Is(proxyErr, context.Canceled) {
		// context.Canceled happens when the downstream client
		// cancels the request, which is not our failure
		return true, nil
	}