Repository: caddyserver/caddy (language: go)

Issue:
v2.7.1: websockets randomly work or don't
Neither Caddy nor my web browser seem to provide any insight into this issue. Caddy logs show nothing relevant (perhaps I need to configure something more verbose/debuggy?) and my browser just Eventually™ says it can Never™ connect to wss://whatever after a long period of time.

---

It seems that in `v2.7.1` it's totally random whether websockets work for any given `reverse_proxy`.

For me: neither my [Send](https://github.com/timvisee/send) (ws for file uploads) nor [TeslaMate](https://github.com/adriankumpf/teslamate) (ws streams in map tiles at vehicle's location in real-time) websockets work. But [The Lounge](https://github.com/thelounge/thelounge)'s websockets work fine for me.

For a buddy: his The Lounge's websockets do not work at all.

---

For both of us: Everything works flawlessly upon rolling back to `v2.7.0-beta.2` (with the same Caddyfile that doesn't seem to work on `v2.7.1`). I don't know what change could cause this random behavior.


Discussion / hints:
Confirmed, we accidentally broke websockets. Sorry about that. We'll make a v2.7.2 hotfix shortly :sweat: 
Perhaps too many things added to 2.7.0 that should've gone through a beta/RC phase first? 😅 

Fix patch:
diff --git a/modules/caddyhttp/reverseproxy/streaming.go b/modules/caddyhttp/reverseproxy/streaming.go
index 3f2489d98bc..71b93860846 100644
--- a/modules/caddyhttp/reverseproxy/streaming.go
+++ b/modules/caddyhttp/reverseproxy/streaming.go
@@ -58,6 +58,13 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrit
 		return
 	}
 
+	// write header first, response headers should not be counted in size
+	// like the rest of handler chain.
+	copyHeader(rw.Header(), res.Header)
+	rw.WriteHeader(res.StatusCode)
+
+	logger.Debug("upgrading connection")
+
 	//nolint:bodyclose
 	conn, brw, hijackErr := http.NewResponseController(rw).Hijack()
 	if errors.Is(hijackErr, http.ErrNotSupported) {
@@ -65,6 +72,11 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrit
 		return
 	}
 
+	if hijackErr != nil {
+		h.logger.Error("hijack failed on protocol switch", zap.Error(hijackErr))
+		return
+	}
+
 	// adopted from https://github.com/golang/go/commit/8bcf2834afdf6a1f7937390903a41518715ef6f5
 	backConnCloseCh := make(chan struct{})
 	go func() {
@@ -78,17 +90,6 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrit
 	}()
 	defer close(backConnCloseCh)
 
-	// write header first, response headers should not be counted in size
-	// like the rest of handler chain.
-	copyHeader(rw.Header(), res.Header)
-	rw.WriteHeader(res.StatusCode)
-
-	logger.Debug("upgrading connection")
-	if hijackErr != nil {
-		h.logger.Error("hijack failed on protocol switch", zap.Error(hijackErr))
-		return
-	}
-
 	start := time.Now()
 	defer func() {
 		conn.Close()


Test patch: