aboutsummaryrefslogtreecommitdiff
path: root/src/net/http/requestwrite_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/http/requestwrite_test.go')
-rw-r--r--src/net/http/requestwrite_test.go218
1 files changed, 214 insertions, 4 deletions
diff --git a/src/net/http/requestwrite_test.go b/src/net/http/requestwrite_test.go
index d13e37aba0..eb65b9f736 100644
--- a/src/net/http/requestwrite_test.go
+++ b/src/net/http/requestwrite_test.go
@@ -5,14 +5,17 @@
package http
import (
+ "bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
+ "net"
"net/url"
"strings"
"testing"
+ "time"
)
type reqWriteTest struct {
@@ -566,6 +569,138 @@ func TestRequestWrite(t *testing.T) {
}
}
+func TestRequestWriteTransport(t *testing.T) {
+ t.Parallel()
+
+ matchSubstr := func(substr string) func(string) error {
+ return func(written string) error {
+ if !strings.Contains(written, substr) {
+ return fmt.Errorf("expected substring %q in request: %s", substr, written)
+ }
+ return nil
+ }
+ }
+
+ noContentLengthOrTransferEncoding := func(req string) error {
+ if strings.Contains(req, "Content-Length: ") {
+ return fmt.Errorf("unexpected Content-Length in request: %s", req)
+ }
+ if strings.Contains(req, "Transfer-Encoding: ") {
+ return fmt.Errorf("unexpected Transfer-Encoding in request: %s", req)
+ }
+ return nil
+ }
+
+ all := func(checks ...func(string) error) func(string) error {
+ return func(req string) error {
+ for _, c := range checks {
+ if err := c(req); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ }
+
+ type testCase struct {
+ method string
+ clen int64 // ContentLength
+ body io.ReadCloser
+ want func(string) error
+
+ // optional:
+ init func(*testCase)
+ afterReqRead func()
+ }
+
+ tests := []testCase{
+ {
+ method: "GET",
+ want: noContentLengthOrTransferEncoding,
+ },
+ {
+ method: "GET",
+ body: ioutil.NopCloser(strings.NewReader("")),
+ want: noContentLengthOrTransferEncoding,
+ },
+ {
+ method: "GET",
+ clen: -1,
+ body: ioutil.NopCloser(strings.NewReader("")),
+ want: noContentLengthOrTransferEncoding,
+ },
+ // A GET with a body, with explicit content length:
+ {
+ method: "GET",
+ clen: 7,
+ body: ioutil.NopCloser(strings.NewReader("foobody")),
+ want: all(matchSubstr("Content-Length: 7"),
+ matchSubstr("foobody")),
+ },
+ // A GET with a body, sniffing the leading "f" from "foobody".
+ {
+ method: "GET",
+ clen: -1,
+ body: ioutil.NopCloser(strings.NewReader("foobody")),
+ want: all(matchSubstr("Transfer-Encoding: chunked"),
+ matchSubstr("\r\n1\r\nf\r\n"),
+ matchSubstr("oobody")),
+ },
+ // But a POST request is expected to have a body, so
+ // no sniffing happens:
+ {
+ method: "POST",
+ clen: -1,
+ body: ioutil.NopCloser(strings.NewReader("foobody")),
+ want: all(matchSubstr("Transfer-Encoding: chunked"),
+ matchSubstr("foobody")),
+ },
+ {
+ method: "POST",
+ clen: -1,
+ body: ioutil.NopCloser(strings.NewReader("")),
+ want: all(matchSubstr("Transfer-Encoding: chunked")),
+ },
+ // Verify that a blocking Request.Body doesn't block forever.
+ {
+ method: "GET",
+ clen: -1,
+ init: func(tt *testCase) {
+ pr, pw := io.Pipe()
+ tt.afterReqRead = func() {
+ pw.Close()
+ }
+ tt.body = ioutil.NopCloser(pr)
+ },
+ want: matchSubstr("Transfer-Encoding: chunked"),
+ },
+ }
+
+ for i, tt := range tests {
+ if tt.init != nil {
+ tt.init(&tt)
+ }
+ req := &Request{
+ Method: tt.method,
+ URL: &url.URL{
+ Scheme: "http",
+ Host: "example.com",
+ },
+ Header: make(Header),
+ ContentLength: tt.clen,
+ Body: tt.body,
+ }
+ got, err := dumpRequestOut(req, tt.afterReqRead)
+ if err != nil {
+ t.Errorf("test[%d]: %v", i, err)
+ continue
+ }
+ if err := tt.want(string(got)); err != nil {
+ t.Errorf("test[%d]: %v", i, err)
+ }
+ }
+}
+
type closeChecker struct {
io.Reader
closed bool
@@ -581,12 +716,14 @@ func (rc *closeChecker) Close() error {
// inside a NopCloser, and that it serializes it correctly.
func TestRequestWriteClosesBody(t *testing.T) {
rc := &closeChecker{Reader: strings.NewReader("my body")}
- req, _ := NewRequest("POST", "http://foo.com/", rc)
- if req.ContentLength != -1 {
- t.Errorf("got req.ContentLength %d, want -1", req.ContentLength)
+ req, err := NewRequest("POST", "http://foo.com/", rc)
+ if err != nil {
+ t.Fatal(err)
}
buf := new(bytes.Buffer)
- req.Write(buf)
+ if err := req.Write(buf); err != nil {
+ t.Error(err)
+ }
if !rc.closed {
t.Error("body not closed after write")
}
@@ -670,3 +807,76 @@ func TestRequestWriteError(t *testing.T) {
t.Fatalf("writeCalls constant is outdated in test")
}
}
+
+// dumpRequestOut is a modified copy of net/http/httputil.DumpRequestOut.
+// Unlike the original, this version doesn't mutate the req.Body and
+// try to restore it. It always dumps the whole body.
+// And it doesn't support https.
+func dumpRequestOut(req *Request, onReadHeaders func()) ([]byte, error) {
+
+ // Use the actual Transport code to record what we would send
+ // on the wire, but not using TCP. Use a Transport with a
+ // custom dialer that returns a fake net.Conn that waits
+ // for the full input (and recording it), and then responds
+ // with a dummy response.
+ var buf bytes.Buffer // records the output
+ pr, pw := io.Pipe()
+ defer pr.Close()
+ defer pw.Close()
+ dr := &delegateReader{c: make(chan io.Reader)}
+
+ t := &Transport{
+ Dial: func(net, addr string) (net.Conn, error) {
+ return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
+ },
+ }
+ defer t.CloseIdleConnections()
+
+ // Wait for the request before replying with a dummy response:
+ go func() {
+ req, err := ReadRequest(bufio.NewReader(pr))
+ if err == nil {
+ if onReadHeaders != nil {
+ onReadHeaders()
+ }
+ // Ensure all the body is read; otherwise
+ // we'll get a partial dump.
+ io.Copy(ioutil.Discard, req.Body)
+ req.Body.Close()
+ }
+ dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
+ }()
+
+ _, err := t.RoundTrip(req)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+// delegateReader is a reader that delegates to another reader,
+// once it arrives on a channel.
+type delegateReader struct {
+ c chan io.Reader
+ r io.Reader // nil until received from c
+}
+
+func (r *delegateReader) Read(p []byte) (int, error) {
+ if r.r == nil {
+ r.r = <-r.c
+ }
+ return r.r.Read(p)
+}
+
+// dumpConn is a net.Conn that writes to Writer and reads from Reader.
+type dumpConn struct {
+ io.Writer
+ io.Reader
+}
+
+func (c *dumpConn) Close() error { return nil }
+func (c *dumpConn) LocalAddr() net.Addr { return nil }
+func (c *dumpConn) RemoteAddr() net.Addr { return nil }
+func (c *dumpConn) SetDeadline(t time.Time) error { return nil }
+func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil }
+func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }