1package middleware
2
3import (
4 "bufio"
5 "context"
6 "crypto/tls"
7 "io"
8 "net"
9 "net/http"
10 "net/http/httptest"
11 "net/http/httputil"
12 "net/url"
13 "strings"
14 "sync"
15 "sync/atomic"
16 "testing"
17 "time"
18
19 "github.com/stretchr/testify/assert"
20 "github.com/stretchr/testify/require"
21)
22
23const testIdle = 300 * time.Millisecond
24
25func newIdleServer(t *testing.T, handler http.Handler) *httptest.Server {
26 t.Helper()
27 srv := httptest.NewUnstartedServer(TransferIdleTimeout(testIdle)(handler))
28 srv.Config.ReadHeaderTimeout = 5 * time.Second
29 srv.Start()
30 t.Cleanup(srv.Close)
31 return srv
32}
33
34func TestTransferIdleTimeout_SlowActiveDownloadCompletes(t *testing.T) {
35 const chunks = 12 // 12 x 50ms = 600ms, twice the idle timeout
36 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
37 for range chunks {
38 _, _ = w.Write([]byte("x"))
39 w.(http.Flusher).Flush()
40 time.Sleep(50 * time.Millisecond)
41 }
42 }))
43
44 start := time.Now()
45 resp, err := http.Get(srv.URL)
46 require.NoError(t, err)
47 defer resp.Body.Close()
48 body, err := io.ReadAll(resp.Body)
49 require.NoError(t, err)
50 assert.Len(t, body, chunks)
51 assert.Greater(t, time.Since(start), testIdle, "transfer must outlast the idle timeout")
52}
53
54func TestTransferIdleTimeout_SilentResponseIsCut(t *testing.T) {
55 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
56 _, _ = w.Write([]byte("start"))
57 w.(http.Flusher).Flush()
58 time.Sleep(3 * testIdle)
59 _, _ = w.Write([]byte("late"))
60 w.(http.Flusher).Flush()
61 }))
62
63 resp, err := http.Get(srv.URL)
64 require.NoError(t, err)
65 defer resp.Body.Close()
66 body, err := io.ReadAll(resp.Body)
67 require.Error(t, err, "connection must be cut after the idle timeout")
68 assert.Equal(t, "start", string(body))
69}
70
71func TestTransferIdleTimeout_SilentHandlerBeforeFirstByteIsCut(t *testing.T) {
72 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
73 time.Sleep(3 * testIdle)
74 _, _ = w.Write([]byte("late"))
75 }))
76
77 resp, err := http.Get(srv.URL)
78 if err == nil {
79 defer resp.Body.Close()
80 _, err = io.ReadAll(resp.Body)
81 }
82 require.Error(t, err)
83}
84
85func TestTransferIdleTimeout_SlowActiveUploadCompletes(t *testing.T) {
86 const chunks = 10 // 10 x 60ms = 600ms
87 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88 n, err := io.Copy(io.Discard, r.Body)
89 if err != nil {
90 http.Error(w, err.Error(), http.StatusBadRequest)
91 return
92 }
93 _, _ = w.Write([]byte(strings.Repeat("o", int(n))))
94 }))
95
96 pr, pw := io.Pipe()
97 go func() {
98 for range chunks {
99 _, _ = pw.Write([]byte("u"))
100 time.Sleep(60 * time.Millisecond)
101 }
102 _ = pw.Close()
103 }()
104 resp, err := http.Post(srv.URL, "application/octet-stream", pr)
105 require.NoError(t, err)
106 defer resp.Body.Close()
107 body, err := io.ReadAll(resp.Body)
108 require.NoError(t, err)
109 assert.Equal(t, http.StatusOK, resp.StatusCode)
110 assert.Len(t, body, chunks)
111}
112
113func TestTransferIdleTimeout_StalledUploadIsCut(t *testing.T) {
114 var readErr atomic.Value
115 done := make(chan struct{})
116 srv := newIdleServer(t, http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
117 defer close(done)
118 _, err := io.Copy(io.Discard, r.Body)
119 if err != nil {
120 readErr.Store(err)
121 }
122 }))
123
124 conn, err := net.Dial("tcp", srv.Listener.Addr().String())
125 require.NoError(t, err)
126 defer conn.Close()
127 _, err = conn.Write([]byte("POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 100\r\n\r\nabc"))
128 require.NoError(t, err)
129
130 select {
131 case <-done:
132 case <-time.After(5 * testIdle):
133 t.Fatal("stalled upload was not cut")
134 }
135 assert.NotNil(t, readErr.Load(), "body read must fail after the idle timeout")
136}
137
138func TestTransferIdleTimeout_PreservesFlusherAndUnwrap(t *testing.T) {
139 var isFlusher, unwrapped bool
140 h := TransferIdleTimeout(time.Second)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
141 _, isFlusher = w.(http.Flusher)
142 _, unwrapped = w.(interface{ Unwrap() http.ResponseWriter })
143 }))
144 h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
145 assert.True(t, isFlusher)
146 assert.True(t, unwrapped)
147}
148
149func TestTransferIdleTimeout_HijackStillWorks(t *testing.T) {
150 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
151 hj, ok := w.(http.Hijacker)
152 if !ok {
153 http.Error(w, "no hijacker", http.StatusInternalServerError)
154 return
155 }
156 conn, rw, err := hj.Hijack()
157 if err != nil {
158 return
159 }
160 defer conn.Close()
161 _, _ = rw.WriteString("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: test\r\n\r\n")
162 _ = rw.Flush()
163 // Stay open longer than the idle timeout: hijacked streams are exempt.
164 time.Sleep(2 * testIdle)
165 _, _ = rw.WriteString("hello")
166 _ = rw.Flush()
167 }))
168
169 conn, err := net.Dial("tcp", srv.Listener.Addr().String())
170 require.NoError(t, err)
171 defer conn.Close()
172 _, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: x\r\nConnection: Upgrade\r\nUpgrade: test\r\n\r\n"))
173 require.NoError(t, err)
174
175 br := bufio.NewReader(conn)
176 resp, err := http.ReadResponse(br, nil)
177 require.NoError(t, err)
178 assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode)
179 buf := make([]byte, 5)
180 _, err = io.ReadFull(br, buf)
181 require.NoError(t, err)
182 assert.Equal(t, "hello", string(buf))
183}
184
185func TestTransferIdleTimeout_ReverseProxyFlushesStream(t *testing.T) {
186 // Flush propagation through a wrapped writer, as used by httputil.ReverseProxy.
187 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
188 rc := http.NewResponseController(w)
189 for range 8 {
190 _, _ = w.Write([]byte("."))
191 if err := rc.Flush(); err != nil {
192 return
193 }
194 time.Sleep(50 * time.Millisecond)
195 }
196 }))
197 resp, err := http.Get(srv.URL)
198 require.NoError(t, err)
199 defer resp.Body.Close()
200 body, err := io.ReadAll(resp.Body)
201 require.NoError(t, err)
202 assert.Len(t, body, 8)
203}
204
205func TestTransferIdleTimeout_DisabledWhenNonPositive(t *testing.T) {
206 called := false
207 inner := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called = true })
208 h := TransferIdleTimeout(0)(inner)
209 h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
210 assert.True(t, called)
211}
212
213func TestTransferIdleTimeout_HTTP2(t *testing.T) {
214 const chunks = 12
215 srv := httptest.NewUnstartedServer(TransferIdleTimeout(testIdle)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
216 for range chunks {
217 _, _ = w.Write([]byte("x"))
218 w.(http.Flusher).Flush()
219 time.Sleep(50 * time.Millisecond)
220 }
221 })))
222 srv.EnableHTTP2 = true
223 srv.StartTLS()
224 defer srv.Close()
225
226 client := srv.Client()
227 resp, err := client.Get(srv.URL)
228 require.NoError(t, err)
229 defer resp.Body.Close()
230 assert.Equal(t, 2, resp.ProtoMajor)
231 body, err := io.ReadAll(resp.Body)
232 require.NoError(t, err)
233 assert.Len(t, body, chunks)
234}
235
236// pipeListener serves connections created with dial over net.Pipe, which has no
237// kernel buffering: a write completes only when the peer reads it.
238type pipeListener struct {
239 conns chan net.Conn
240 done chan struct{}
241}
242
243func newPipeListener() *pipeListener {
244 return &pipeListener{conns: make(chan net.Conn), done: make(chan struct{})}
245}
246
247func (l *pipeListener) Accept() (net.Conn, error) {
248 select {
249 case c := <-l.conns:
250 return c, nil
251 case <-l.done:
252 return nil, net.ErrClosed
253 }
254}
255
256func (l *pipeListener) Close() error {
257 select {
258 case <-l.done:
259 default:
260 close(l.done)
261 }
262 return nil
263}
264
265func (l *pipeListener) Addr() net.Addr { return pipeAddr{} }
266
267func (l *pipeListener) dial() net.Conn {
268 client, server := net.Pipe()
269 l.conns <- server
270 return client
271}
272
273type pipeAddr struct{}
274
275func (pipeAddr) Network() string { return "pipe" }
276func (pipeAddr) String() string { return "pipe" }
277
278// slowReadConn reads at most 4 KiB every pause, like a congested client.
279type slowReadConn struct {
280 net.Conn
281 pause time.Duration
282}
283
284func (c *slowReadConn) Read(p []byte) (int, error) {
285 time.Sleep(c.pause)
286 return c.Conn.Read(p[:min(len(p), 4096)])
287}
288
289func servePipe(t *testing.T, handler http.Handler) *pipeListener {
290 t.Helper()
291 ln := newPipeListener()
292 srv := &http.Server{Handler: TransferIdleTimeout(testIdle)(handler), ReadHeaderTimeout: 5 * time.Second}
293 go func() { _ = srv.Serve(ln) }()
294 t.Cleanup(func() { _ = srv.Close() })
295 return ln
296}
297
298func TestTransferIdleTimeout_BackpressuredButReadingClientIsNotCut(t *testing.T) {
299 const size = 64 * 1024
300 ln := servePipe(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
301 _, _ = w.Write([]byte(strings.Repeat("d", size)))
302 }))
303 client := &http.Client{Transport: &http.Transport{
304 DisableKeepAlives: true,
305 DialContext: func(context.Context, string, string) (net.Conn, error) {
306 return &slowReadConn{Conn: ln.dial(), pause: 25 * time.Millisecond}, nil
307 },
308 }}
309
310 start := time.Now()
311 resp, err := client.Get("http://pipe/")
312 require.NoError(t, err)
313 defer resp.Body.Close()
314 body, err := io.ReadAll(resp.Body)
315 require.NoError(t, err, "a client that keeps reading must not be cut")
316 assert.Len(t, body, size)
317 assert.Greater(t, time.Since(start), testIdle, "the transfer must outlast the idle timeout")
318}
319
320func TestTransferIdleTimeout_NonReadingClientUnblocksWrite(t *testing.T) {
321 writeErr := make(chan error, 1)
322 ln := servePipe(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
323 _, err := w.Write([]byte(strings.Repeat("d", 1<<20)))
324 writeErr <- err
325 }))
326 conn := ln.dial()
327 defer conn.Close()
328 _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: x\r\n\r\n"))
329 require.NoError(t, err)
330
331 select {
332 case err := <-writeErr:
333 require.Error(t, err, "write to a client that never reads must fail")
334 case <-time.After(10 * testIdle):
335 t.Fatal("blocked write was not unblocked by the inactivity timeout")
336 }
337}
338
339func TestTransferIdleTimeout_SilentHandlerContextCanceledAndConnectionClosed(t *testing.T) {
340 ctxDone := make(chan struct{})
341 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
342 _, _ = w.Write([]byte("start"))
343 w.(http.Flusher).Flush()
344 <-r.Context().Done() // a ReverseProxy would abort its upstream request here
345 close(ctxDone)
346 }))
347
348 client := &http.Client{Timeout: 10 * time.Second}
349 begin := time.Now()
350 resp, err := client.Get(srv.URL)
351 require.NoError(t, err)
352 defer resp.Body.Close()
353 body, err := io.ReadAll(resp.Body)
354 require.Error(t, err, "the client connection must be closed, not completed cleanly")
355 assert.Equal(t, "start", string(body))
356 assert.Less(t, time.Since(begin), 5*time.Second)
357
358 select {
359 case <-ctxDone:
360 case <-time.After(5 * time.Second):
361 t.Fatal("request context was not canceled")
362 }
363}
364
365func TestTransferIdleTimeout_ProgressKeepsContextAlive(t *testing.T) {
366 var canceled atomic.Bool
367 srv := newIdleServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
368 for range 12 {
369 _, _ = w.Write([]byte("x"))
370 w.(http.Flusher).Flush()
371 select {
372 case <-r.Context().Done():
373 canceled.Store(true)
374 return
375 case <-time.After(50 * time.Millisecond):
376 }
377 }
378 }))
379 resp, err := http.Get(srv.URL)
380 require.NoError(t, err)
381 defer resp.Body.Close()
382 body, err := io.ReadAll(resp.Body)
383 require.NoError(t, err)
384 assert.Len(t, body, 12)
385 assert.False(t, canceled.Load())
386}
387
388func TestTransferIdleTimeout_BodyUseAfterHandlerReturnedIsSafe(t *testing.T) {
389 var body io.ReadCloser
390 h := TransferIdleTimeout(time.Second)(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
391 body = r.Body
392 }))
393 req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("payload"))
394 h.ServeHTTP(httptest.NewRecorder(), req)
395
396 // Like http.Transport closing the outbound body after the handler returned.
397 require.NoError(t, body.Close())
398 require.NoError(t, body.Close(), "Close must be idempotent")
399 _, _ = body.Read(make([]byte, 4))
400}
401
402// TestTransferIdleTimeout_HTTP2ReverseProxyEarlyResponseDuringUpload covers the
403// transport closing the outbound request body after the handler returned, when
404// the upstream answers early (413) while the client is still uploading. Run
405// with -race.
406func TestTransferIdleTimeout_HTTP2ReverseProxyEarlyResponseDuringUpload(t *testing.T) {
407 upstream := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
408 http.Error(w, "too large", http.StatusRequestEntityTooLarge)
409 }))
410 upstream.EnableHTTP2 = true
411 upstream.StartTLS()
412 defer upstream.Close()
413
414 target, err := url.Parse(upstream.URL)
415 require.NoError(t, err)
416 proxy := httputil.NewSingleHostReverseProxy(target)
417 proxy.Transport = &http.Transport{
418 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // test upstream
419 ForceAttemptHTTP2: true,
420 }
421 front := httptest.NewUnstartedServer(TransferIdleTimeout(testIdle)(proxy))
422 front.EnableHTTP2 = true
423 front.StartTLS()
424 defer front.Close()
425
426 var wg sync.WaitGroup
427 for range 8 {
428 wg.Add(1)
429 go func() {
430 defer wg.Done()
431 for range 10 {
432 uploadUntilEarlyResponse(t, front)
433 }
434 }()
435 }
436 wg.Wait()
437}
438
439func uploadUntilEarlyResponse(t *testing.T, front *httptest.Server) {
440 t.Helper()
441 pr, pw := io.Pipe()
442 writerDone := make(chan struct{})
443 go func() {
444 defer close(writerDone)
445 chunk := []byte(strings.Repeat("u", 4096))
446 for range 200 {
447 if _, err := pw.Write(chunk); err != nil {
448 return
449 }
450 time.Sleep(time.Millisecond)
451 }
452 _ = pw.Close()
453 }()
454
455 defer func() {
456 _ = pr.CloseWithError(io.ErrClosedPipe)
457 <-writerDone
458 }()
459
460 req, err := http.NewRequest(http.MethodPost, front.URL, pr)
461 if !assert.NoError(t, err) {
462 return
463 }
464 resp, err := front.Client().Do(req)
465 if !assert.NoError(t, err, "the early 413 must reach the client") {
466 return
467 }
468 defer resp.Body.Close()
469 assert.Equal(t, 2, resp.ProtoMajor)
470 assert.Equal(t, http.StatusRequestEntityTooLarge, resp.StatusCode)
471 _, _ = io.Copy(io.Discard, resp.Body)
472}
473
474// TestTransferIdleTimeout_HTTP2BodyUsedAfterHandlerReturned uses the request
475// body after net/http's HTTP/2 server finished the handler (it drops its
476// response writer state then), as http.Transport does when it closes an
477// outbound body asynchronously. It must neither panic nor race.
478func TestTransferIdleTimeout_HTTP2BodyUsedAfterHandlerReturned(t *testing.T) {
479 bodyCh := make(chan io.ReadCloser, 1)
480 returned := make(chan struct{})
481 inner := TransferIdleTimeout(testIdle)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
482 bodyCh <- r.Body
483 _, _ = w.Write([]byte("early"))
484 }))
485 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
486 inner.ServeHTTP(w, r)
487 close(returned)
488 }))
489 srv.EnableHTTP2 = true
490 srv.StartTLS()
491 defer srv.Close()
492
493 pr, pw := io.Pipe()
494 defer pw.Close()
495 req, err := http.NewRequest(http.MethodPost, srv.URL, pr)
496 require.NoError(t, err)
497 go func() { _, _ = pw.Write([]byte("partial")) }()
498 resp, err := srv.Client().Do(req)
499 require.NoError(t, err)
500 _, _ = io.Copy(io.Discard, resp.Body)
501 _ = resp.Body.Close()
502
503 body := <-bodyCh
504 <-returned
505 time.Sleep(100 * time.Millisecond) // let the server loop process handlerDone
506
507 _, _ = body.Read(make([]byte, 8))
508 require.NoError(t, body.Close())
509 require.NoError(t, body.Close())
510}
511
512// trackedPipeServer serves net.Pipe connections and reports when the server
513// side closes one, so tests can assert a stalled connection is released.
514func trackedPipeServer(t *testing.T, handler http.Handler) (*pipeListener, <-chan struct{}) {
515 t.Helper()
516 closed := make(chan struct{})
517 var once sync.Once
518 ln := newPipeListener()
519 srv := &http.Server{
520 Handler: TransferIdleTimeout(testIdle)(handler),
521 ReadHeaderTimeout: 5 * time.Second,
522 ConnState: func(_ net.Conn, state http.ConnState) {
523 if state == http.StateClosed {
524 once.Do(func() { close(closed) })
525 }
526 },
527 }
528 go func() { _ = srv.Serve(ln) }()
529 t.Cleanup(func() { _ = srv.Close() })
530 return ln, closed
531}
532
533func requireClosed(t *testing.T, closed <-chan struct{}, what string) {
534 t.Helper()
535 select {
536 case <-closed:
537 case <-time.After(20 * testIdle):
538 t.Fatalf("server connection still held: %s", what)
539 }
540}
541
542// The handler returns with a small unflushed response; net/http's final flush
543// then blocks on a client that never reads (net.Pipe has no buffering).
544func TestTransferIdleTimeout_FinalFlushToNonReadingClientIsBounded(t *testing.T) {
545 ln, closed := trackedPipeServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
546 _, _ = w.Write([]byte("small"))
547 }))
548 conn := ln.dial()
549 defer conn.Close()
550 _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: x\r\n\r\n"))
551 require.NoError(t, err)
552
553 requireClosed(t, closed, "final response flush to a non-reading client")
554}
555
556// The client declares a body it never sends and the handler answers without
557// reading it; net/http then tries to drain the unread body.
558func TestTransferIdleTimeout_UnreadDeclaredBodyDrainIsBounded(t *testing.T) {
559 ln, closed := trackedPipeServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
560 http.Error(w, "unauthorized", http.StatusUnauthorized)
561 }))
562 conn := ln.dial()
563 defer conn.Close()
564 go func() { _, _ = io.Copy(io.Discard, conn) }() // read the response, then stay silent
565 _, err := conn.Write([]byte("POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 100\r\n\r\n"))
566 require.NoError(t, err)
567
568 requireClosed(t, closed, "drain of an unread declared request body")
569}
570
571func TestTransferIdleTimeout_UnreadDeclaredBodyDrainIsBoundedOverTCP(t *testing.T) {
572 closed := make(chan struct{})
573 var once sync.Once
574 srv := httptest.NewUnstartedServer(TransferIdleTimeout(testIdle)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
575 http.Error(w, "unauthorized", http.StatusUnauthorized)
576 })))
577 srv.Config.ConnState = func(_ net.Conn, state http.ConnState) {
578 if state == http.StateClosed {
579 once.Do(func() { close(closed) })
580 }
581 }
582 srv.Start()
583 defer srv.Close()
584
585 conn, err := net.Dial("tcp", srv.Listener.Addr().String())
586 require.NoError(t, err)
587 defer conn.Close()
588 _, err = conn.Write([]byte("POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 100\r\n\r\n"))
589 require.NoError(t, err)
590
591 requireClosed(t, closed, "drain of an unread declared request body over TCP")
592}
593
594// Leftover deadlines armed at handler return must not leak into the next
595// keep-alive request on the same connection.
596func TestTransferIdleTimeout_KeepAliveNextRequestNotAffectedByLeftoverDeadlines(t *testing.T) {
597 srv := httptest.NewUnstartedServer(TransferIdleTimeout(testIdle)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
598 _, _ = io.Copy(io.Discard, r.Body)
599 _, _ = w.Write([]byte("ok"))
600 })))
601 srv.Config.IdleTimeout = 10 * time.Second
602 srv.Start()
603 defer srv.Close()
604
605 client := &http.Client{Transport: &http.Transport{MaxConnsPerHost: 1}}
606 for i := range 3 {
607 resp, err := client.Post(srv.URL, "text/plain", strings.NewReader("body"))
608 require.NoError(t, err, "request %d", i)
609 _, err = io.ReadAll(resp.Body)
610 require.NoError(t, err)
611 _ = resp.Body.Close()
612 time.Sleep(2 * testIdle) // outlive the leftover deadline while the connection idles
613 }
614}
615
616// Hijack flushes pending headers; it must not hold the timer's mutex while it
617// blocks on a client that does not read.
618func TestTransferIdleTimeout_HijackBlockedOnNonReadingClientIsBounded(t *testing.T) {
619 hijackErr := make(chan error, 1)
620 ln := servePipe(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
621 w.WriteHeader(http.StatusOK)
622 conn, _, err := w.(http.Hijacker).Hijack()
623 if conn != nil {
624 _ = conn.Close()
625 }
626 hijackErr <- err
627 }))
628 conn := ln.dial()
629 defer conn.Close()
630 _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: x\r\n\r\n"))
631 require.NoError(t, err)
632
633 select {
634 case <-hijackErr:
635 case <-time.After(20 * testIdle):
636 t.Fatal("Hijack blocked on a non-reading client was not bounded")
637 }
638}