Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) (
resp.Body = nil
defer func() {
if err != nil {
if resp.StatusCode == http.StatusSwitchingProtocols {
// The body is the upgraded connection, not an HTTP body.
// Reading it waits for data the server may never send.
respBody.Close()
resp.Body = http.NoBody
return
}

// We read a bit of the body for easier debugging.
r := io.LimitReader(respBody, 1024)

Expand Down
59 changes: 59 additions & 0 deletions dial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,62 @@ func TestDialViaProxy(t *testing.T) {
assertEcho(t, ctx, c)
assertClose(t, c)
}

func TestDialFailedHandshakeBody(t *testing.T) {
t.Parallel()

t.Run("switchingProtocols", func(t *testing.T) {
t.Parallel()

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, brw, err := http.NewResponseController(w).Hijack()
if err != nil {
t.Errorf("failed to hijack: %v", err)
return
}
defer conn.Close()
brw.WriteString("HTTP/1.1 101 Switching Protocols\r\n" +
"Connection: Upgrade\r\n" +
"Upgrade: websocket\r\n" +
"Sec-WebSocket-Accept: xd\r\n\r\n")
brw.Flush()
// Send nothing more and wait for the client to close.
io.Copy(io.Discard, conn)
}))
defer s.Close()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

start := time.Now()
_, resp, err := websocket.Dial(ctx, s.URL, nil)
elapsed := time.Since(start)
assert.Contains(t, err, "invalid Sec-WebSocket-Accept")
assert.Equal(t, "StatusCode", http.StatusSwitchingProtocols, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
assert.Success(t, err)
assert.Equal(t, "body", "", string(b))
if elapsed > time.Second {
t.Fatalf("Dial took %v, want under 1s", elapsed)
}
})

t.Run("forbidden", func(t *testing.T) {
t.Parallel()

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "forbidden by test", http.StatusForbidden)
}))
defer s.Close()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

_, resp, err := websocket.Dial(ctx, s.URL, nil)
assert.Error(t, err)
assert.Equal(t, "StatusCode", http.StatusForbidden, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
assert.Success(t, err)
assert.Equal(t, "body", "forbidden by test\n", string(b))
})
}
Loading