"io/ioutil"
"net/http/internal"
"net/textproto"
+ "reflect"
"sort"
"strconv"
"strings"
if t.ContentLength < 0 && len(t.TransferEncoding) == 0 && t.shouldSendChunkedRequestBody() {
t.TransferEncoding = []string{"chunked"}
}
+ // If there's a body, conservatively flush the headers
+ // to any bufio.Writer we're writing to, just in case
+ // the server needs the headers early, before we copy
+ // the body and possibly block. We make an exception
+ // for the common standard library in-memory types,
+ // though, to avoid unnecessary TCP packets on the
+ // wire. (Issue 22088.)
+ if t.ContentLength != 0 && !isKnownInMemoryReader(t.Body) {
+ t.FlushHeaders = true
+ }
+
atLeastHTTP11 = true // Transport requests are always 1.1 or 2.0
case *Response:
t.IsResponse = true
}
return
}
+
+var nopCloserType = reflect.TypeOf(ioutil.NopCloser(nil))
+
+// isKnownInMemoryReader reports whether r is a type known to not
+// block on Read. Its caller uses this as an optional optimization to
+// send fewer TCP packets.
+func isKnownInMemoryReader(r io.Reader) bool {
+ switch r.(type) {
+ case *bytes.Reader, *bytes.Buffer, *strings.Reader:
+ return true
+ }
+ if reflect.TypeOf(r) == nopCloserType {
+ return isKnownInMemoryReader(reflect.ValueOf(r).Field(0).Interface().(io.Reader))
+ }
+ return false
+}
import (
"bufio"
+ "bytes"
"io"
+ "io/ioutil"
"strings"
"testing"
)
t.Errorf("buf = %q; want %q", buf, want)
}
}
+
+func TestDetectInMemoryReaders(t *testing.T) {
+ pr, _ := io.Pipe()
+ tests := []struct {
+ r io.Reader
+ want bool
+ }{
+ {pr, false},
+
+ {bytes.NewReader(nil), true},
+ {bytes.NewBuffer(nil), true},
+ {strings.NewReader(""), true},
+
+ {ioutil.NopCloser(pr), false},
+
+ {ioutil.NopCloser(bytes.NewReader(nil)), true},
+ {ioutil.NopCloser(bytes.NewBuffer(nil)), true},
+ {ioutil.NopCloser(strings.NewReader("")), true},
+ }
+ for i, tt := range tests {
+ got := isKnownInMemoryReader(tt.r)
+ if got != tt.want {
+ t.Logf("%d: got = %v; want %v", i, got, tt.want)
+ }
+ }
+}
defer res.Body.Close()
want := []string{
- "POST / HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: x\r\nTransfer-Encoding: chunked\r\nAccept-Encoding: gzip\r\n\r\n" +
- "5\r\nnum0\n\r\n",
+ "POST / HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: x\r\nTransfer-Encoding: chunked\r\nAccept-Encoding: gzip\r\n\r\n",
+ "5\r\nnum0\n\r\n",
"5\r\nnum1\n\r\n",
"5\r\nnum2\n\r\n",
"0\r\n\r\n",
}
}
+// Issue 22088: flush Transport request headers if we're not sure the body won't block on read.
+func TestTransportFlushesRequestHeader(t *testing.T) {
+ defer afterTest(t)
+ gotReq := make(chan struct{})
+ cst := newClientServerTest(t, h1Mode, HandlerFunc(func(w ResponseWriter, r *Request) {
+ close(gotReq)
+ }))
+ defer cst.close()
+
+ pr, pw := io.Pipe()
+ req, err := NewRequest("POST", cst.ts.URL, pr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ gotRes := make(chan struct{})
+ go func() {
+ defer close(gotRes)
+ res, err := cst.tr.RoundTrip(req)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ res.Body.Close()
+ }()
+
+ select {
+ case <-gotReq:
+ pw.Close()
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for handler to get request")
+ }
+ <-gotRes
+}
+
// Issue 11745.
func TestTransportPrefersResponseOverWriteError(t *testing.T) {
if testing.Short() {