mirror of https://go.googlesource.com/go
344 lines
10 KiB
Go
344 lines
10 KiB
Go
// 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"
|
|
"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 {
|
|
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) {
|
|
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)
|
|
}
|
|
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()
|
|
b, _ := io.ReadAll(res.Body)
|
|
if string(b) != "one" {
|
|
t.Errorf("unexpected body: %q", string(b))
|
|
}
|
|
}
|
|
|
|
func TestResponseControllerSetFutureWriteDeadline(t *testing.T) {
|
|
run(t, testResponseControllerSetFutureWriteDeadline)
|
|
}
|
|
func testResponseControllerSetFutureWriteDeadline(t *testing.T, mode testMode) {
|
|
errc := make(chan error, 1)
|
|
startwritec := make(chan struct{})
|
|
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)
|
|
}
|
|
<-startwritec // don't set the deadline until the client reads response headers
|
|
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
|
|
}))
|
|
|
|
res, err := cst.c.Get(cst.ts.URL)
|
|
close(startwritec)
|
|
if err != nil {
|
|
t.Fatalf("unexpected connection error: %v", err)
|
|
}
|
|
defer res.Body.Close()
|
|
_, err = io.Copy(io.Discard, res.Body)
|
|
if err == nil {
|
|
t.Errorf("client reading from truncated request body: got nil error, want non-nil")
|
|
}
|
|
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()
|
|
}
|