mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
fix(adapt): an informational status goes out when it is written, so coder/websocket's 101 reaches the client
Claude-Session: https://claude.ai/code/session_01AGuBEqCjuman4E9FdKEgeV
This commit is contained in:
+20
-7
@@ -168,7 +168,7 @@ func serveUpgrade(c *Ctx, h http.Handler, req *http.Request) {
|
||||
hdr: make(http.Header),
|
||||
}
|
||||
h.ServeHTTP(w, req)
|
||||
if !w.taken {
|
||||
if !w.taken && !w.switched {
|
||||
w.answer()
|
||||
}
|
||||
})
|
||||
@@ -179,17 +179,30 @@ func serveUpgrade(c *Ctx, h http.Handler, req *http.Request) {
|
||||
// — an unauthorized socket is refused before it opens — has its status, headers
|
||||
// and body written as an ordinary HTTP/1.1 reply, and the connection closes.
|
||||
type upgradeWriter struct {
|
||||
conn net.Conn
|
||||
brw *bufio.ReadWriter
|
||||
hdr http.Header
|
||||
status int
|
||||
body bytes.Buffer
|
||||
taken bool
|
||||
conn net.Conn
|
||||
brw *bufio.ReadWriter
|
||||
hdr http.Header
|
||||
status int
|
||||
body bytes.Buffer
|
||||
taken bool
|
||||
switched bool // a 101 went out: the reply is the handler's from here
|
||||
}
|
||||
|
||||
func (w *upgradeWriter) Header() http.Header { return w.hdr }
|
||||
|
||||
// WriteHeader records a final status and SENDS an informational one, as net/http
|
||||
// does: coder/websocket writes its 101 with WriteHeader and only then hijacks, so
|
||||
// a 101 that waited for the handler to return would never reach the client and
|
||||
// the handshake would hang with the socket open.
|
||||
func (w *upgradeWriter) WriteHeader(code int) {
|
||||
if code >= 100 && code < 200 && !w.taken {
|
||||
fmt.Fprintf(w.brw, "HTTP/1.1 %d %s\r\n", code, http.StatusText(code))
|
||||
_ = w.hdr.Write(w.brw)
|
||||
_, _ = w.brw.WriteString("\r\n")
|
||||
_ = w.brw.Flush()
|
||||
w.switched = w.switched || code == http.StatusSwitchingProtocols
|
||||
return
|
||||
}
|
||||
if w.status == 0 {
|
||||
w.status = code
|
||||
}
|
||||
|
||||
@@ -204,3 +204,47 @@ func TestAdaptNetHTTP_AnUpgradeTakesTheConnection(t *testing.T) {
|
||||
t.Fatalf("the refusal lost its body: %q", rest)
|
||||
}
|
||||
}
|
||||
|
||||
// THE 101 GOES OUT WHEN IT IS WRITTEN. coder/websocket sets its headers, calls
|
||||
// WriteHeader(101) and only then hijacks, relying on net/http to have sent an
|
||||
// informational status at once. Held until the handler returned, the client never
|
||||
// saw the switch and the handshake hung — the talk socket's second failure.
|
||||
func TestAdaptNetHTTP_ASwitchWrittenBeforeTheHijackIsSent(t *testing.T) {
|
||||
base := streamApp(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Upgrade", "echo")
|
||||
w.Header().Set("Connection", "Upgrade")
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
conn, brw, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
t.Errorf("hijack: %v", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
line, _ := brw.ReadString('\n')
|
||||
_, _ = brw.WriteString("echo " + line)
|
||||
_ = brw.Flush()
|
||||
})
|
||||
conn, err := net.Dial("tcp", strings.TrimPrefix(base, "http://"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
fmt.Fprint(conn, "GET /legacy/socket HTTP/1.1\r\nHost: x\r\nConnection: Upgrade\r\nUpgrade: echo\r\n\r\n")
|
||||
rd := bufio.NewReader(conn)
|
||||
status, err := rd.ReadString('\n')
|
||||
if err != nil || !strings.HasPrefix(status, "HTTP/1.1 101") {
|
||||
t.Fatalf("the switch answered %q (%v), want a 101 before any frame", status, err)
|
||||
}
|
||||
head := map[string]bool{}
|
||||
for line, _ := rd.ReadString('\n'); line != "\r\n" && line != ""; line, _ = rd.ReadString('\n') {
|
||||
head[strings.ToLower(strings.TrimSpace(line))] = true
|
||||
}
|
||||
if !head["upgrade: echo"] {
|
||||
t.Fatalf("the 101 lost the handler's headers: %v", head)
|
||||
}
|
||||
fmt.Fprint(conn, "ping\n")
|
||||
if got, _ := rd.ReadString('\n'); got != "echo ping\n" {
|
||||
t.Fatalf("after the switch the connection answered %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user