2012-03-27 23:13:14 +00:00
|
|
|
// Copyright 2011 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 httputil
|
|
|
|
|
|
|
|
import (
|
2017-04-10 11:32:00 +00:00
|
|
|
"bufio"
|
2012-03-27 23:13:14 +00:00
|
|
|
"bytes"
|
2022-10-27 18:55:19 +00:00
|
|
|
"context"
|
2012-03-27 23:13:14 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2022-10-27 18:55:19 +00:00
|
|
|
"math/rand"
|
2012-03-27 23:13:14 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2015-08-28 15:33:40 +00:00
|
|
|
"runtime"
|
2022-10-27 18:55:19 +00:00
|
|
|
"runtime/pprof"
|
2015-08-28 15:33:40 +00:00
|
|
|
"strings"
|
2012-03-27 23:13:14 +00:00
|
|
|
"testing"
|
2022-10-27 18:55:19 +00:00
|
|
|
"time"
|
2012-03-27 23:13:14 +00:00
|
|
|
)
|
|
|
|
|
2022-10-27 18:55:19 +00:00
|
|
|
type eofReader struct{}
|
|
|
|
|
|
|
|
func (n eofReader) Close() error { return nil }
|
|
|
|
|
|
|
|
func (n eofReader) Read([]byte) (int, error) { return 0, io.EOF }
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
type dumpTest struct {
|
2022-10-27 18:55:19 +00:00
|
|
|
// Either Req or GetReq can be set/nil but not both.
|
|
|
|
Req *http.Request
|
|
|
|
GetReq func() *http.Request
|
|
|
|
|
|
|
|
Body any // optional []byte or func() io.ReadCloser to populate Req.Body
|
2012-03-27 23:13:14 +00:00
|
|
|
|
|
|
|
WantDump string
|
|
|
|
WantDumpOut string
|
2022-10-27 18:55:19 +00:00
|
|
|
MustError bool // if true, the test is expected to throw an error
|
2014-09-21 17:33:12 +00:00
|
|
|
NoBody bool // if true, set DumpRequest{,Out} body to false
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var dumpTests = []dumpTest{
|
|
|
|
// HTTP/1.1 => chunked coding; body; empty trailer
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: &http.Request{
|
2012-03-27 23:13:14 +00:00
|
|
|
Method: "GET",
|
|
|
|
URL: &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: "www.google.com",
|
|
|
|
Path: "/search",
|
|
|
|
},
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
TransferEncoding: []string{"chunked"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Body: []byte("abcdef"),
|
|
|
|
|
|
|
|
WantDump: "GET /search HTTP/1.1\r\n" +
|
|
|
|
"Host: www.google.com\r\n" +
|
|
|
|
"Transfer-Encoding: chunked\r\n\r\n" +
|
|
|
|
chunk("abcdef") + chunk(""),
|
|
|
|
},
|
|
|
|
|
|
|
|
// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
|
|
|
|
// and doesn't add a User-Agent.
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: &http.Request{
|
2012-03-27 23:13:14 +00:00
|
|
|
Method: "GET",
|
|
|
|
URL: mustParseURL("/foo"),
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 0,
|
|
|
|
Header: http.Header{
|
|
|
|
"X-Foo": []string{"X-Bar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
WantDump: "GET /foo HTTP/1.0\r\n" +
|
|
|
|
"X-Foo: X-Bar\r\n\r\n",
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: mustNewRequest("GET", "http://example.com/foo", nil),
|
2012-03-27 23:13:14 +00:00
|
|
|
|
|
|
|
WantDumpOut: "GET /foo HTTP/1.1\r\n" +
|
|
|
|
"Host: example.com\r\n" +
|
2017-04-10 11:32:00 +00:00
|
|
|
"User-Agent: Go-http-client/1.1\r\n" +
|
2012-03-27 23:13:14 +00:00
|
|
|
"Accept-Encoding: gzip\r\n\r\n",
|
|
|
|
},
|
|
|
|
|
|
|
|
// Test that an https URL doesn't try to do an SSL negotiation
|
|
|
|
// with a bytes.Buffer and hang with all goroutines not
|
|
|
|
// runnable.
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: mustNewRequest("GET", "https://example.com/foo", nil),
|
2012-03-27 23:13:14 +00:00
|
|
|
WantDumpOut: "GET /foo HTTP/1.1\r\n" +
|
|
|
|
"Host: example.com\r\n" +
|
2017-04-10 11:32:00 +00:00
|
|
|
"User-Agent: Go-http-client/1.1\r\n" +
|
2012-03-27 23:13:14 +00:00
|
|
|
"Accept-Encoding: gzip\r\n\r\n",
|
|
|
|
},
|
2014-09-21 17:33:12 +00:00
|
|
|
|
|
|
|
// Request with Body, but Dump requested without it.
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: &http.Request{
|
2014-09-21 17:33:12 +00:00
|
|
|
Method: "POST",
|
|
|
|
URL: &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: "post.tld",
|
|
|
|
Path: "/",
|
|
|
|
},
|
|
|
|
ContentLength: 6,
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
},
|
|
|
|
|
|
|
|
Body: []byte("abcdef"),
|
|
|
|
|
|
|
|
WantDumpOut: "POST / HTTP/1.1\r\n" +
|
|
|
|
"Host: post.tld\r\n" +
|
2017-04-10 11:32:00 +00:00
|
|
|
"User-Agent: Go-http-client/1.1\r\n" +
|
2014-09-21 17:33:12 +00:00
|
|
|
"Content-Length: 6\r\n" +
|
|
|
|
"Accept-Encoding: gzip\r\n\r\n",
|
|
|
|
|
|
|
|
NoBody: true,
|
|
|
|
},
|
2015-08-28 15:33:40 +00:00
|
|
|
|
|
|
|
// Request with Body > 8196 (default buffer size)
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: &http.Request{
|
2015-08-28 15:33:40 +00:00
|
|
|
Method: "POST",
|
|
|
|
URL: &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: "post.tld",
|
|
|
|
Path: "/",
|
|
|
|
},
|
2017-10-07 00:16:47 +00:00
|
|
|
Header: http.Header{
|
|
|
|
"Content-Length": []string{"8193"},
|
|
|
|
},
|
|
|
|
|
2015-08-28 15:33:40 +00:00
|
|
|
ContentLength: 8193,
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
},
|
|
|
|
|
|
|
|
Body: bytes.Repeat([]byte("a"), 8193),
|
|
|
|
|
|
|
|
WantDumpOut: "POST / HTTP/1.1\r\n" +
|
|
|
|
"Host: post.tld\r\n" +
|
2017-04-10 11:32:00 +00:00
|
|
|
"User-Agent: Go-http-client/1.1\r\n" +
|
2015-08-28 15:33:40 +00:00
|
|
|
"Content-Length: 8193\r\n" +
|
|
|
|
"Accept-Encoding: gzip\r\n\r\n" +
|
|
|
|
strings.Repeat("a", 8193),
|
2017-10-07 00:16:47 +00:00
|
|
|
WantDump: "POST / HTTP/1.1\r\n" +
|
|
|
|
"Host: post.tld\r\n" +
|
|
|
|
"Content-Length: 8193\r\n\r\n" +
|
|
|
|
strings.Repeat("a", 8193),
|
2015-08-28 15:33:40 +00:00
|
|
|
},
|
2017-04-10 11:32:00 +00:00
|
|
|
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
GetReq: func() *http.Request {
|
|
|
|
return mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" +
|
|
|
|
"User-Agent: blah\r\n\r\n")
|
|
|
|
},
|
2017-04-10 11:32:00 +00:00
|
|
|
NoBody: true,
|
|
|
|
WantDump: "GET http://foo.com/ HTTP/1.1\r\n" +
|
|
|
|
"User-Agent: blah\r\n\r\n",
|
|
|
|
},
|
2017-10-07 00:16:47 +00:00
|
|
|
|
|
|
|
// Issue #7215. DumpRequest should return the "Content-Length" when set
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
GetReq: func() *http.Request {
|
|
|
|
return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
|
|
|
|
"Host: passport.myhost.com\r\n" +
|
|
|
|
"Content-Length: 3\r\n" +
|
|
|
|
"\r\nkey1=name1&key2=name2")
|
|
|
|
},
|
2017-10-07 00:16:47 +00:00
|
|
|
WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
|
|
|
|
"Host: passport.myhost.com\r\n" +
|
|
|
|
"Content-Length: 3\r\n" +
|
|
|
|
"\r\nkey",
|
|
|
|
},
|
|
|
|
// Issue #7215. DumpRequest should return the "Content-Length" in ReadRequest
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
GetReq: func() *http.Request {
|
|
|
|
return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
|
|
|
|
"Host: passport.myhost.com\r\n" +
|
|
|
|
"Content-Length: 0\r\n" +
|
|
|
|
"\r\nkey1=name1&key2=name2")
|
|
|
|
},
|
2017-10-07 00:16:47 +00:00
|
|
|
WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
|
|
|
|
"Host: passport.myhost.com\r\n" +
|
|
|
|
"Content-Length: 0\r\n\r\n",
|
|
|
|
},
|
|
|
|
|
|
|
|
// Issue #7215. DumpRequest should not return the "Content-Length" if unset
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
GetReq: func() *http.Request {
|
|
|
|
return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
|
|
|
|
"Host: passport.myhost.com\r\n" +
|
|
|
|
"\r\nkey1=name1&key2=name2")
|
|
|
|
},
|
2017-10-07 00:16:47 +00:00
|
|
|
WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
|
|
|
|
"Host: passport.myhost.com\r\n\r\n",
|
|
|
|
},
|
|
|
|
|
|
|
|
// Issue 18506: make drainBody recognize NoBody. Otherwise
|
|
|
|
// this was turning into a chunked request.
|
|
|
|
{
|
2022-10-27 18:55:19 +00:00
|
|
|
Req: mustNewRequest("POST", "http://example.com/foo", http.NoBody),
|
2017-10-07 00:16:47 +00:00
|
|
|
WantDumpOut: "POST /foo HTTP/1.1\r\n" +
|
|
|
|
"Host: example.com\r\n" +
|
|
|
|
"User-Agent: Go-http-client/1.1\r\n" +
|
|
|
|
"Content-Length: 0\r\n" +
|
|
|
|
"Accept-Encoding: gzip\r\n\r\n",
|
|
|
|
},
|
2022-10-27 18:55:19 +00:00
|
|
|
|
|
|
|
// Issue 34504: a non-nil Body without ContentLength set should be chunked
|
|
|
|
{
|
|
|
|
Req: &http.Request{
|
|
|
|
Method: "PUT",
|
|
|
|
URL: &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: "post.tld",
|
|
|
|
Path: "/test",
|
|
|
|
},
|
|
|
|
ContentLength: 0,
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
Body: &eofReader{},
|
|
|
|
},
|
|
|
|
NoBody: true,
|
|
|
|
WantDumpOut: "PUT /test HTTP/1.1\r\n" +
|
|
|
|
"Host: post.tld\r\n" +
|
|
|
|
"User-Agent: Go-http-client/1.1\r\n" +
|
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
"Accept-Encoding: gzip\r\n\r\n",
|
|
|
|
},
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDumpRequest(t *testing.T) {
|
2022-10-27 18:55:19 +00:00
|
|
|
// Make a copy of dumpTests and add 10 new cases with an empty URL
|
|
|
|
// to test that no goroutines are leaked. See golang.org/issue/32571.
|
|
|
|
// 10 seems to be a decent number which always triggers the failure.
|
|
|
|
dumpTests := dumpTests[:]
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
dumpTests = append(dumpTests, dumpTest{
|
|
|
|
Req: mustNewRequest("GET", "", nil),
|
|
|
|
MustError: true,
|
|
|
|
})
|
|
|
|
}
|
2015-08-28 15:33:40 +00:00
|
|
|
numg0 := runtime.NumGoroutine()
|
2012-03-27 23:13:14 +00:00
|
|
|
for i, tt := range dumpTests {
|
2022-10-27 18:55:19 +00:00
|
|
|
if tt.Req != nil && tt.GetReq != nil || tt.Req == nil && tt.GetReq == nil {
|
|
|
|
t.Errorf("#%d: either .Req(%p) or .GetReq(%p) can be set/nil but not both", i, tt.Req, tt.GetReq)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
freshReq := func(ti dumpTest) *http.Request {
|
|
|
|
req := ti.Req
|
|
|
|
if req == nil {
|
|
|
|
req = ti.GetReq()
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.Header == nil {
|
|
|
|
req.Header = make(http.Header)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ti.Body == nil {
|
|
|
|
return req
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
switch b := ti.Body.(type) {
|
2012-03-27 23:13:14 +00:00
|
|
|
case []byte:
|
2022-10-27 18:55:19 +00:00
|
|
|
req.Body = io.NopCloser(bytes.NewReader(b))
|
2012-03-27 23:13:14 +00:00
|
|
|
case func() io.ReadCloser:
|
2022-10-27 18:55:19 +00:00
|
|
|
req.Body = b()
|
2015-08-28 15:33:40 +00:00
|
|
|
default:
|
2022-10-27 18:55:19 +00:00
|
|
|
t.Fatalf("Test %d: unsupported Body of %T", i, ti.Body)
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
return req
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if tt.WantDump != "" {
|
2022-10-27 18:55:19 +00:00
|
|
|
req := freshReq(tt)
|
|
|
|
dump, err := DumpRequest(req, !tt.NoBody)
|
2012-03-27 23:13:14 +00:00
|
|
|
if err != nil {
|
2022-10-27 18:55:19 +00:00
|
|
|
t.Errorf("DumpRequest #%d: %s\nWantDump:\n%s", i, err, tt.WantDump)
|
2012-03-27 23:13:14 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if string(dump) != tt.WantDump {
|
|
|
|
t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-27 18:55:19 +00:00
|
|
|
if tt.MustError {
|
|
|
|
req := freshReq(tt)
|
|
|
|
_, err := DumpRequestOut(req, !tt.NoBody)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("DumpRequestOut #%d: expected an error, got nil", i)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2012-03-27 23:13:14 +00:00
|
|
|
if tt.WantDumpOut != "" {
|
2022-10-27 18:55:19 +00:00
|
|
|
req := freshReq(tt)
|
|
|
|
dump, err := DumpRequestOut(req, !tt.NoBody)
|
2012-03-27 23:13:14 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("DumpRequestOut #%d: %s", i, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if string(dump) != tt.WantDumpOut {
|
|
|
|
t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
|
|
|
|
// Validate we haven't leaked any goroutines.
|
|
|
|
var dg int
|
|
|
|
dl := deadline(t, 5*time.Second, time.Second)
|
|
|
|
for time.Now().Before(dl) {
|
|
|
|
if dg = runtime.NumGoroutine() - numg0; dg <= 4 {
|
|
|
|
// No unexpected goroutines.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allow goroutines to schedule and die off.
|
|
|
|
runtime.Gosched()
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, 4096)
|
|
|
|
buf = buf[:runtime.Stack(buf, true)]
|
|
|
|
t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
// deadline returns the time which is needed before t.Deadline()
|
|
|
|
// if one is configured and it is s greater than needed in the future,
|
|
|
|
// otherwise defaultDelay from the current time.
|
|
|
|
func deadline(t *testing.T, defaultDelay, needed time.Duration) time.Time {
|
|
|
|
if dl, ok := t.Deadline(); ok {
|
|
|
|
if dl = dl.Add(-needed); dl.After(time.Now()) {
|
|
|
|
// Allow an arbitrarily long delay.
|
|
|
|
return dl
|
|
|
|
}
|
2015-08-28 15:33:40 +00:00
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
|
|
|
|
// No deadline configured or its closer than needed from now
|
|
|
|
// so just use the default.
|
|
|
|
return time.Now().Add(defaultDelay)
|
2012-03-27 23:13:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func chunk(s string) string {
|
|
|
|
return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustParseURL(s string) *url.URL {
|
|
|
|
u, err := url.Parse(s)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
|
|
|
|
}
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustNewRequest(method, url string, body io.Reader) *http.Request {
|
|
|
|
req, err := http.NewRequest(method, url, body)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err))
|
|
|
|
}
|
|
|
|
return req
|
|
|
|
}
|
2015-08-28 15:33:40 +00:00
|
|
|
|
2017-04-10 11:32:00 +00:00
|
|
|
func mustReadRequest(s string) *http.Request {
|
|
|
|
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s)))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
2015-08-28 15:33:40 +00:00
|
|
|
var dumpResTests = []struct {
|
|
|
|
res *http.Response
|
|
|
|
body bool
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
res: &http.Response{
|
|
|
|
Status: "200 OK",
|
|
|
|
StatusCode: 200,
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
ContentLength: 50,
|
|
|
|
Header: http.Header{
|
|
|
|
"Foo": []string{"Bar"},
|
|
|
|
},
|
2022-10-27 18:55:19 +00:00
|
|
|
Body: io.NopCloser(strings.NewReader("foo")), // shouldn't be used
|
2015-08-28 15:33:40 +00:00
|
|
|
},
|
|
|
|
body: false, // to verify we see 50, not empty or 3.
|
|
|
|
want: `HTTP/1.1 200 OK
|
|
|
|
Content-Length: 50
|
|
|
|
Foo: Bar`,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
res: &http.Response{
|
|
|
|
Status: "200 OK",
|
|
|
|
StatusCode: 200,
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
ContentLength: 3,
|
2022-10-27 18:55:19 +00:00
|
|
|
Body: io.NopCloser(strings.NewReader("foo")),
|
2015-08-28 15:33:40 +00:00
|
|
|
},
|
|
|
|
body: true,
|
|
|
|
want: `HTTP/1.1 200 OK
|
|
|
|
Content-Length: 3
|
|
|
|
|
|
|
|
foo`,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
res: &http.Response{
|
|
|
|
Status: "200 OK",
|
|
|
|
StatusCode: 200,
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
ContentLength: -1,
|
2022-10-27 18:55:19 +00:00
|
|
|
Body: io.NopCloser(strings.NewReader("foo")),
|
2015-08-28 15:33:40 +00:00
|
|
|
TransferEncoding: []string{"chunked"},
|
|
|
|
},
|
|
|
|
body: true,
|
|
|
|
want: `HTTP/1.1 200 OK
|
|
|
|
Transfer-Encoding: chunked
|
|
|
|
|
|
|
|
3
|
|
|
|
foo
|
|
|
|
0`,
|
|
|
|
},
|
2017-10-07 00:16:47 +00:00
|
|
|
{
|
|
|
|
res: &http.Response{
|
|
|
|
Status: "200 OK",
|
|
|
|
StatusCode: 200,
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
ContentLength: 0,
|
|
|
|
Header: http.Header{
|
|
|
|
// To verify if headers are not filtered out.
|
|
|
|
"Foo1": []string{"Bar1"},
|
|
|
|
"Foo2": []string{"Bar2"},
|
|
|
|
},
|
|
|
|
Body: nil,
|
|
|
|
},
|
|
|
|
body: false, // to verify we see 0, not empty.
|
|
|
|
want: `HTTP/1.1 200 OK
|
|
|
|
Foo1: Bar1
|
|
|
|
Foo2: Bar2
|
|
|
|
Content-Length: 0`,
|
|
|
|
},
|
2015-08-28 15:33:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDumpResponse(t *testing.T) {
|
|
|
|
for i, tt := range dumpResTests {
|
|
|
|
gotb, err := DumpResponse(tt.res, tt.body)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%d. DumpResponse = %v", i, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
got := string(gotb)
|
|
|
|
got = strings.TrimSpace(got)
|
2019-06-02 15:48:37 +00:00
|
|
|
got = strings.ReplaceAll(got, "\r", "")
|
2015-08-28 15:33:40 +00:00
|
|
|
|
|
|
|
if got != tt.want {
|
|
|
|
t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-27 18:55:19 +00:00
|
|
|
|
|
|
|
// Issue 38352: Check for deadlock on canceled requests.
|
|
|
|
func TestDumpRequestOutIssue38352(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
timeout := 10 * time.Second
|
|
|
|
if deadline, ok := t.Deadline(); ok {
|
|
|
|
timeout = time.Until(deadline)
|
|
|
|
timeout -= time.Second * 2 // Leave 2 seconds to report failures.
|
|
|
|
}
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
delay := time.Duration(rand.Intn(5)) * time.Millisecond
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), delay)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
r := bytes.NewBuffer(make([]byte, 10000))
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://example.com", r)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
out := make(chan error)
|
|
|
|
go func() {
|
|
|
|
_, err = DumpRequestOut(req, true)
|
|
|
|
out <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-out:
|
|
|
|
case <-time.After(timeout):
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
fmt.Fprintf(b, "deadlock detected on iteration %d after %s with delay: %v\n", i, timeout, delay)
|
|
|
|
pprof.Lookup("goroutine").WriteTo(b, 1)
|
|
|
|
t.Fatal(b.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|