| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package http_test | |
| import ( | |
| "errors" | |
| "fmt" | |
| "internal/nettest" | |
| "io" | |
| . "net/http" | |
| "os" | |
| "sync" | |
| "testing" | |
| "time" | |
| ) | |
| func TestResponseControllerFlush(t *testing.T) { run(t, testResponseControllerFlush) } | |
| func testResponseControllerFlush(t *testing.T, mode testMode) { | |
| continuec := make(chan struct{}) | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { | |
| ctl := NewResponseController(w) | |
| w.Write([]byte("one")) | |
| if err := ctl.Flush(); err != nil { | |
| t.Errorf("ctl.Flush() = %v, want nil", err) | |
| return | |
| } | |
| <-continuec | |
| w.Write([]byte("two")) | |
| })) | |
| res, err := cst.c.Get(cst.ts.URL) | |
| if err != nil { | |
| t.Fatalf("unexpected connection error: %v", err) | |
| } | |
| defer res.Body.Close() | |
| buf := make([]byte, 16) | |
| n, err := res.Body.Read(buf) | |
| close(continuec) | |
| if err != nil || string(buf[:n]) != "one" { | |
| t.Fatalf("Body.Read = %q, %v, want %q, nil", string(buf[:n]), err, "one") | |
| } | |
| got, err := io.ReadAll(res.Body) | |
| if err != nil || string(got) != "two" { | |
| t.Fatalf("Body.Read = %q, %v, want %q, nil", string(got), err, "two") | |
| } | |
| } | |
| func TestResponseControllerHijack(t *testing.T) { run(t, testResponseControllerHijack) } | |
| func testResponseControllerHijack(t *testing.T, mode testMode) { | |
| const header = "X-Header" | |
| const value = "set" | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { | |
| ctl := NewResponseController(w) | |
| c, _, err := ctl.Hijack() | |
| if mode == http2Mode || mode == http3Mode { | |
| if err == nil { | |
| t.Errorf("ctl.Hijack = nil, want error") | |
| } | |
| w.Header().Set(header, value) | |
| return | |
| } | |
| if err != nil { | |
| t.Errorf("ctl.Hijack = _, _, %v, want _, _, nil", err) | |
| return | |
| } | |
| fmt.Fprintf(c, "HTTP/1.0 200 OK\r\n%v: %v\r\nContent-Length: 0\r\n\r\n", header, value) | |
| })) | |
| res, err := cst.c.Get(cst.ts.URL) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if got, want := res.Header.Get(header), value; got != want { | |
| t.Errorf("response header %q = %q, want %q", header, got, want) | |
| } | |
| } | |
| func TestResponseControllerSetPastWriteDeadline(t *testing.T) { | |
| run(t, testResponseControllerSetPastWriteDeadline) | |
| } | |
| func testResponseControllerSetPastWriteDeadline(t *testing.T, mode testMode) { | |
| readOne := make(chan struct{}) | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { | |
| ctl := NewResponseController(w) | |
| w.Write([]byte("one")) | |
| if err := ctl.Flush(); err != nil { | |
| t.Errorf("before setting deadline: ctl.Flush() = %v, want nil", err) | |
| } | |
| <-readOne | |
| if err := ctl.SetWriteDeadline(time.Now().Add(-10 * time.Second)); err != nil { | |
| t.Errorf("ctl.SetWriteDeadline() = %v, want nil", err) | |
| } | |
| w.Write([]byte("two")) | |
| if err := ctl.Flush(); err == nil { | |
| t.Errorf("after setting deadline: ctl.Flush() = nil, want non-nil") | |
| } | |
| // Connection errors are sticky, so resetting the deadline does not permit | |
| // making more progress. We might want to change this in the future, but verify | |
| // the current behavior for now. If we do change this, we'll want to make sure | |
| // to do so only for writing the response body, not headers. | |
| if err := ctl.SetWriteDeadline(time.Now().Add(1 * time.Hour)); err != nil { | |
| t.Errorf("ctl.SetWriteDeadline() = %v, want nil", err) | |
| } | |
| w.Write([]byte("three")) | |
| if err := ctl.Flush(); err == nil { | |
| t.Errorf("after resetting deadline: ctl.Flush() = nil, want non-nil") | |
| } | |
| })) | |
| res, err := cst.c.Get(cst.ts.URL) | |
| if err != nil { | |
| t.Fatalf("unexpected connection error: %v", err) | |
| } | |
| defer res.Body.Close() | |
| buf := make([]byte, 3) | |
| if _, err := io.ReadFull(res.Body, buf); err != nil || string(buf) != "one" { | |
| t.Fatalf("Body.Read = %q, %v; want %q, nil", string(buf), err, "one") | |
| } | |
| close(readOne) | |
| b, err := io.ReadAll(res.Body) | |
| if err == nil { | |
| t.Errorf("Body.Read error = nil; want non-nil after server write deadline") | |
| } | |
| if len(b) > 0 { | |
| t.Errorf("Body.Read after server write deadline = %q; want empty", string(b)) | |
| } | |
| } | |
| func TestResponseControllerSetFutureWriteDeadline(t *testing.T) { | |
| runSynctest(t, testResponseControllerSetFutureWriteDeadline) | |
| } | |
| func testResponseControllerSetFutureWriteDeadline(t *testing.T, mode testMode) { | |
| errc := make(chan error, 1) | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { | |
| ctl := NewResponseController(w) | |
| w.WriteHeader(200) | |
| if err := ctl.Flush(); err != nil { | |
| t.Errorf("ctl.Flush() = %v, want nil", err) | |
| } | |
| if err := ctl.SetWriteDeadline(time.Now().Add(1 * time.Millisecond)); err != nil { | |
| t.Errorf("ctl.SetWriteDeadline() = %v, want nil", err) | |
| } | |
| _, err := io.Copy(w, neverEnding('a')) | |
| errc <- err | |
| })) | |
| // Set a buffer size large enough to hold the response headers, | |
| // small enough to block writing the body before long. | |
| cst.setDialNettestHook(func(nc *nettest.Conn) { | |
| nc.SetReadBufferSize(10000) | |
| }) | |
| res, err := cst.c.Get(cst.ts.URL) | |
| if err != nil { | |
| t.Fatalf("unexpected connection error: %v", err) | |
| } | |
| defer res.Body.Close() | |
| err = <-errc // io.Copy error | |
| if !errors.Is(err, os.ErrDeadlineExceeded) { | |
| t.Errorf("server timed out writing request body: got err %v; want os.ErrDeadlineExceeded", err) | |
| } | |
| } | |
| func TestResponseControllerSetPastReadDeadline(t *testing.T) { | |
| run(t, testResponseControllerSetPastReadDeadline) | |
| } | |
| func testResponseControllerSetPastReadDeadline(t *testing.T, mode testMode) { | |
| readc := make(chan struct{}) | |
| donec := make(chan struct{}) | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { | |
| defer close(donec) | |
| ctl := NewResponseController(w) | |
| b := make([]byte, 3) | |
| n, err := io.ReadFull(r.Body, b) | |
| b = b[:n] | |
| if err != nil || string(b) != "one" { | |
| t.Errorf("before setting read deadline: Read = %v, %q, want nil, %q", err, string(b), "one") | |
| return | |
| } | |
| if err := ctl.SetReadDeadline(time.Now()); err != nil { | |
| t.Errorf("ctl.SetReadDeadline() = %v, want nil", err) | |
| return | |
| } | |
| b, err = io.ReadAll(r.Body) | |
| if err == nil || string(b) != "" { | |
| t.Errorf("after setting read deadline: Read = %q, nil, want error", string(b)) | |
| } | |
| close(readc) | |
| // Connection errors are sticky, so resetting the deadline does not permit | |
| // making more progress. We might want to change this in the future, but verify | |
| // the current behavior for now. | |
| if err := ctl.SetReadDeadline(time.Time{}); err != nil { | |
| t.Errorf("ctl.SetReadDeadline() = %v, want nil", err) | |
| return | |
| } | |
| b, err = io.ReadAll(r.Body) | |
| if err == nil { | |
| t.Errorf("after resetting read deadline: Read = %q, nil, want error", string(b)) | |
| } | |
| })) | |
| pr, pw := io.Pipe() | |
| var wg sync.WaitGroup | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| defer pw.Close() | |
| pw.Write([]byte("one")) | |
| select { | |
| case <-readc: | |
| case <-donec: | |
| select { | |
| case <-readc: | |
| default: | |
| t.Errorf("server handler unexpectedly exited without closing readc") | |
| return | |
| } | |
| } | |
| pw.Write([]byte("two")) | |
| }() | |
| defer wg.Wait() | |
| res, err := cst.c.Post(cst.ts.URL, "text/foo", pr) | |
| if err == nil { | |
| defer res.Body.Close() | |
| } | |
| } | |
| func TestResponseControllerSetFutureReadDeadline(t *testing.T) { | |
| run(t, testResponseControllerSetFutureReadDeadline) | |
| } | |
| func testResponseControllerSetFutureReadDeadline(t *testing.T, mode testMode) { | |
| respBody := "response body" | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, req *Request) { | |
| ctl := NewResponseController(w) | |
| if err := ctl.SetReadDeadline(time.Now().Add(1 * time.Millisecond)); err != nil { | |
| t.Errorf("ctl.SetReadDeadline() = %v, want nil", err) | |
| } | |
| _, err := io.Copy(io.Discard, req.Body) | |
| if !errors.Is(err, os.ErrDeadlineExceeded) { | |
| t.Errorf("server timed out reading request body: got err %v; want os.ErrDeadlineExceeded", err) | |
| } | |
| w.Write([]byte(respBody)) | |
| })) | |
| pr, pw := io.Pipe() | |
| res, err := cst.c.Post(cst.ts.URL, "text/apocryphal", pr) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer res.Body.Close() | |
| got, err := io.ReadAll(res.Body) | |
| if string(got) != respBody || err != nil { | |
| t.Errorf("client read response body: %q, %v; want %q, nil", string(got), err, respBody) | |
| } | |
| pw.Close() | |
| } | |
| type wrapWriter struct { | |
| ResponseWriter | |
| } | |
| func (w wrapWriter) Unwrap() ResponseWriter { | |
| return w.ResponseWriter | |
| } | |
| func TestWrappedResponseController(t *testing.T) { run(t, testWrappedResponseController) } | |
| func testWrappedResponseController(t *testing.T, mode testMode) { | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { | |
| w = wrapWriter{w} | |
| ctl := NewResponseController(w) | |
| if err := ctl.Flush(); err != nil { | |
| t.Errorf("ctl.Flush() = %v, want nil", err) | |
| } | |
| if err := ctl.SetReadDeadline(time.Time{}); err != nil { | |
| t.Errorf("ctl.SetReadDeadline() = %v, want nil", err) | |
| } | |
| if err := ctl.SetWriteDeadline(time.Time{}); err != nil { | |
| t.Errorf("ctl.SetWriteDeadline() = %v, want nil", err) | |
| } | |
| })) | |
| res, err := cst.c.Get(cst.ts.URL) | |
| if err != nil { | |
| t.Fatalf("unexpected connection error: %v", err) | |
| } | |
| io.Copy(io.Discard, res.Body) | |
| defer res.Body.Close() | |
| } | |
| func TestResponseControllerEnableFullDuplex(t *testing.T) { | |
| run(t, testResponseControllerEnableFullDuplex) | |
| } | |
| func testResponseControllerEnableFullDuplex(t *testing.T, mode testMode) { | |
| cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, req *Request) { | |
| ctl := NewResponseController(w) | |
| if err := ctl.EnableFullDuplex(); err != nil { | |
| // TODO: Drop test for HTTP/2 when x/net is updated to support | |
| // EnableFullDuplex. Since HTTP/2 supports full duplex by default, | |
| // the rest of the test is fine; it's just the EnableFullDuplex call | |
| // that fails. | |
| if mode != http2Mode { | |
| t.Errorf("ctl.EnableFullDuplex() = %v, want nil", err) | |
| } | |
| } | |
| w.WriteHeader(200) | |
| ctl.Flush() | |
| for { | |
| var buf [1]byte | |
| n, err := req.Body.Read(buf[:]) | |
| if n != 1 || err != nil { | |
| break | |
| } | |
| w.Write(buf[:]) | |
| ctl.Flush() | |
| } | |
| })) | |
| pr, pw := io.Pipe() | |
| res, err := cst.c.Post(cst.ts.URL, "text/apocryphal", pr) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer res.Body.Close() | |
| for i := byte(0); i < 10; i++ { | |
| if _, err := pw.Write([]byte{i}); err != nil { | |
| t.Fatalf("Write: %v", err) | |
| } | |
| var buf [1]byte | |
| if n, err := res.Body.Read(buf[:]); n != 1 || err != nil { | |
| t.Fatalf("Read: %v, %v", n, err) | |
| } | |
| if buf[0] != i { | |
| t.Fatalf("read byte %v, want %v", buf[0], i) | |
| } | |
| } | |
| pw.Close() | |
| } | |
| func TestIssue58237(t *testing.T) { | |
| cst := newClientServerTest(t, http2Mode, HandlerFunc(func(w ResponseWriter, req *Request) { | |
| ctl := NewResponseController(w) | |
| if err := ctl.SetReadDeadline(time.Now().Add(1 * time.Millisecond)); err != nil { | |
| t.Errorf("ctl.SetReadDeadline() = %v, want nil", err) | |
| } | |
| time.Sleep(10 * time.Millisecond) | |
| })) | |
| res, err := cst.c.Get(cst.ts.URL) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| res.Body.Close() | |
| } |