A plain request to a WebSocket address is answered 426, not handed to the upgrader as a server fault

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-10-01 16:20:25 -07:00
co-authored by Hanzo Dev
parent a99640c25d
commit 863682e102
2 changed files with 54 additions and 0 deletions
+9
View File
@@ -67,6 +67,15 @@ func Upgrade(fn Handler, opts ...Config) zip.Handler {
// of a server that tries to upgrade every request it receives.
return zip.Terminal("wsx.Upgrade", func(c *zip.Ctx) error {
rc := c.Fiber().RequestCtx()
// A plain request to a WebSocket address is the client's mistake — a
// crawler, a health probe, a browser tab opened on the URL. Answered 426
// (RFC 9110 §15.5.22) with the protocol to switch to, as a client error,
// rather than handed to the upgrader, whose handshake error is reported
// as a server fault.
if !websocket.FastHTTPIsWebSocketUpgrade(rc) {
c.SetHeader("Upgrade", "websocket")
return zip.Errorf(fasthttp.StatusUpgradeRequired, "this address speaks WebSocket: connect with an Upgrade: websocket request")
}
return up.Upgrade(rc, func(ws *Conn) {
_ = fn(ws)
})
+45
View File
@@ -0,0 +1,45 @@
package wsx_test
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/zap-proto/zip"
"github.com/zap-proto/zip/wsx"
)
// A plain GET to a WebSocket address — a crawler, a probe, a tab opened on the
// URL — is the client's mistake: 426 with the protocol to switch to, never the
// upgrader's handshake error surfacing as a server fault.
func TestAPlainRequestIsTold426(t *testing.T) {
app := zip.New(zip.Config{AppName: "wsx", DisableStartupMessage: true})
reached := false
app.Raw(http.MethodGet, "/ws", wsx.Upgrade(func(*wsx.Conn) error {
reached = true
return nil
}))
if err := app.Build(); err != nil {
t.Fatalf("Build: %v", err)
}
resp, err := app.Fiber().Test(httptest.NewRequest(http.MethodGet, "/ws", nil))
if err != nil {
t.Fatalf("GET: %v", err)
}
defer func() { _ = resp.Body.Close() }()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusUpgradeRequired {
t.Fatalf("status = %d (%s), want 426", resp.StatusCode, body)
}
if got := resp.Header.Get("Upgrade"); got != "websocket" {
t.Fatalf("Upgrade header = %q, want websocket", got)
}
if !strings.Contains(string(body), "WebSocket") {
t.Fatalf("body does not say what to do: %s", body)
}
if reached {
t.Fatal("the handler ran for a request that never upgraded")
}
}