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

Code before:
	// after the roundtrip returns an error)
	if succ, ok := proxyErr.(roundtripSucceeded); ok {
		return true, succ.error
	}

	// remember this failure (if enabled)
	h.countFailure(upstream)

	// if we've tried long enough, break
	if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
		return true, proxyErr
	}

	return false, proxyErr
}

const canonicalWsHeaderPrefix = "Sec-Websocket"

// websocket header replacer, normally server doesn't care about this difference,
// but some do, and RFC use the case too
// gorilla/websocket, gobwas/ws all use this case too.
// see https://caddy.community/t/websockets-fail-venus-os-cerbo-gx-victron/25685
var wsHeaderReplacer = strings.NewReplacer(canonicalWsHeaderPrefix, "Sec-WebSocket")

func normalizeWsHeader(header http.Header) {
	for k, v := range header {
		if strings.HasPrefix(k, canonicalWsHeaderPrefix) {
			header[wsHeaderReplacer.Replace(k)] = v
			delete(header, k)
		}
	}
}

// prepareRequest clones req so that it can be safely modified without
// changing the original request or introducing data races. It then
// modifies it so that it is ready to be proxied, except for directing
// to a specific upstream. This method adjusts headers and other relevant
// properties of the cloned request and should be done just once (before
// proxying) regardless of proxy retries. This assumes that no mutations
// of the cloned request are performed by h during or after proxying.
func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.Request, error) {
	req = cloneRequest(req)

	// if enabled, perform rewrites on the cloned request; if
	// the method is GET or HEAD, prevent the request body
	// from being copied to the upstream
	if h.Rewrite != nil {
		changed := h.Rewrite.Rewrite(req, repl)
		if changed && (h.Rewrite.Method == "GET" || h.Rewrite.Method == "HEAD") {
			req.ContentLength = 0
			req.Body = nil

Review comment (suggestion):
There's only exactly 5 headers we need to care about. I think if we directly check for those headers, it would perform better, because it would avoid having to do string comparisons on every header field.

Code after:
	// after the roundtrip returns an error)
	if succ, ok := proxyErr.(roundtripSucceeded); ok {
		return true, succ.error
	}

	// remember this failure (if enabled)
	h.countFailure(upstream)

	// if we've tried long enough, break
	if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
		return true, proxyErr
	}

	return false, proxyErr
}

// Mapping of the canonical form of the headers, to the RFC 6455 form,
// i.e. `WebSocket` with uppercase 'S'.
var websocketHeaderMapping = map[string]string{
	"Sec-Websocket-Accept":     "Sec-WebSocket-Accept",
	"Sec-Websocket-Extensions": "Sec-WebSocket-Extensions",
	"Sec-Websocket-Key":        "Sec-WebSocket-Key",
	"Sec-Websocket-Protocol":   "Sec-WebSocket-Protocol",
	"Sec-Websocket-Version":    "Sec-WebSocket-Version",
}

// normalizeWebsocketHeaders ensures we use the standard casing as per
// RFC 6455, i.e. `WebSocket` with uppercase 'S'. Most servers don't
// care about this difference (read headers case insensitively), but
// some do, so this maximizes compatibility with upstreams.
// See https://github.com/caddyserver/caddy/pull/6621
func normalizeWebsocketHeaders(header http.Header) {
	for k, rk := range websocketHeaderMapping {
		if v, ok := header[k]; ok {
			delete(header, k)
			header[rk] = v
		}
	}
}

// prepareRequest clones req so that it can be safely modified without
// changing the original request or introducing data races. It then
// modifies it so that it is ready to be proxied, except for directing
// to a specific upstream. This method adjusts headers and other relevant
// properties of the cloned request and should be done just once (before
// proxying) regardless of proxy retries. This assumes that no mutations
// of the cloned request are performed by h during or after proxying.
func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.Request, error) {
	req = cloneRequest(req)

	// if enabled, perform rewrites on the cloned request; if