diff options
| author | Rick Hudson <rlh@golang.org> | 2016-04-27 18:19:16 -0400 |
|---|---|---|
| committer | Rick Hudson <rlh@golang.org> | 2016-04-27 18:46:52 -0400 |
| commit | 23aeb34df172b17b7bfaa85fb59ca64bef9073bb (patch) | |
| tree | a8ab866f1e50f0059856ce628f036d93ab620155 /src/net/http | |
| parent | 1354b32cd70f2702381764fd595dd2faa996840c (diff) | |
| parent | d3c79d324acd7300b6f705e66af8ca711af00d9f (diff) | |
| download | go-23aeb34df172b17b7bfaa85fb59ca64bef9073bb.tar.xz | |
[dev.garbage] Merge remote-tracking branch 'origin/master' into HEAD
Change-Id: I282fd9ce9db435dfd35e882a9502ab1abc185297
Diffstat (limited to 'src/net/http')
| -rw-r--r-- | src/net/http/client.go | 5 | ||||
| -rw-r--r-- | src/net/http/client_test.go | 28 | ||||
| -rw-r--r-- | src/net/http/clientserver_test.go | 28 | ||||
| -rw-r--r-- | src/net/http/cookiejar/punycode.go | 2 | ||||
| -rw-r--r-- | src/net/http/fs.go | 10 | ||||
| -rw-r--r-- | src/net/http/fs_test.go | 6 | ||||
| -rw-r--r-- | src/net/http/http.go | 25 | ||||
| -rw-r--r-- | src/net/http/httptest/httptest_test.go | 4 | ||||
| -rw-r--r-- | src/net/http/httputil/dump.go | 1 | ||||
| -rw-r--r-- | src/net/http/httputil/dump_test.go | 40 | ||||
| -rw-r--r-- | src/net/http/httputil/example_test.go | 2 | ||||
| -rw-r--r-- | src/net/http/httputil/persist.go | 10 | ||||
| -rw-r--r-- | src/net/http/main_test.go | 23 | ||||
| -rw-r--r-- | src/net/http/pprof/pprof.go | 9 | ||||
| -rw-r--r-- | src/net/http/request.go | 14 | ||||
| -rw-r--r-- | src/net/http/request_test.go | 2 | ||||
| -rw-r--r-- | src/net/http/response.go | 2 | ||||
| -rw-r--r-- | src/net/http/serve_test.go | 86 | ||||
| -rw-r--r-- | src/net/http/server.go | 73 | ||||
| -rw-r--r-- | src/net/http/transfer.go | 4 | ||||
| -rw-r--r-- | src/net/http/transport.go | 80 | ||||
| -rw-r--r-- | src/net/http/transport_test.go | 51 |
22 files changed, 435 insertions, 70 deletions
diff --git a/src/net/http/client.go b/src/net/http/client.go index 10f5684a79..f8ab675a3d 100644 --- a/src/net/http/client.go +++ b/src/net/http/client.go @@ -110,10 +110,6 @@ type RoundTripper interface { RoundTrip(*Request) (*Response, error) } -// Given a string of the form "host", "host:port", or "[ipv6::address]:port", -// return true if the string includes a port. -func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } - // refererForURL returns a referer without any authentication info or // an empty string if lastReq scheme is https and newReq scheme is http. func refererForURL(lastReq, newReq *url.URL) string { @@ -475,6 +471,7 @@ func (c *Client) doFollowingRedirects(req *Request, shouldRedirect func(int) boo URL: u, Header: make(Header), Cancel: ireq.Cancel, + ctx: ireq.ctx, } if ireq.Method == "POST" || ireq.Method == "PUT" { req.Method = "GET" diff --git a/src/net/http/client_test.go b/src/net/http/client_test.go index e4fed26803..a9b30b1bf5 100644 --- a/src/net/http/client_test.go +++ b/src/net/http/client_test.go @@ -8,6 +8,7 @@ package http_test import ( "bytes" + "context" "crypto/tls" "crypto/x509" "encoding/base64" @@ -290,6 +291,33 @@ func TestClientRedirects(t *testing.T) { } } +func TestClientRedirectContext(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + Redirect(w, r, "/", StatusFound) + })) + defer ts.Close() + + ctx, cancel := context.WithCancel(context.Background()) + c := &Client{CheckRedirect: func(req *Request, via []*Request) error { + cancel() + if len(via) > 2 { + return errors.New("too many redirects") + } + return nil + }} + req, _ := NewRequest("GET", ts.URL, nil) + req = req.WithContext(ctx) + _, err := c.Do(req) + ue, ok := err.(*url.Error) + if !ok { + t.Fatalf("got error %T; want *url.Error", err) + } + if ue.Err != ExportErrRequestCanceled && ue.Err != ExportErrRequestCanceledConn { + t.Errorf("url.Error.Err = %v; want errRequestCanceled or errRequestCanceledConn", ue.Err) + } +} + func TestPostRedirects(t *testing.T) { defer afterTest(t) var log struct { diff --git a/src/net/http/clientserver_test.go b/src/net/http/clientserver_test.go index c2bab378e3..f721382365 100644 --- a/src/net/http/clientserver_test.go +++ b/src/net/http/clientserver_test.go @@ -1123,6 +1123,34 @@ func testBogusStatusWorks(t *testing.T, h2 bool) { } } +func TestInterruptWithPanic_h1(t *testing.T) { testInterruptWithPanic(t, h1Mode) } +func TestInterruptWithPanic_h2(t *testing.T) { testInterruptWithPanic(t, h2Mode) } +func testInterruptWithPanic(t *testing.T, h2 bool) { + log.SetOutput(ioutil.Discard) // is noisy otherwise + defer log.SetOutput(os.Stderr) + + const msg = "hello" + defer afterTest(t) + cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { + io.WriteString(w, msg) + w.(Flusher).Flush() + panic("no more") + })) + defer cst.close() + res, err := cst.c.Get(cst.ts.URL) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + slurp, err := ioutil.ReadAll(res.Body) + if string(slurp) != msg { + t.Errorf("client read %q; want %q", slurp, msg) + } + if err == nil { + t.Errorf("client read all successfully; want some error") + } +} + type noteCloseConn struct { net.Conn closeFunc func() diff --git a/src/net/http/cookiejar/punycode.go b/src/net/http/cookiejar/punycode.go index ea7ceb5ef3..a9cc666e8c 100644 --- a/src/net/http/cookiejar/punycode.go +++ b/src/net/http/cookiejar/punycode.go @@ -37,7 +37,7 @@ func encode(prefix, s string) (string, error) { delta, n, bias := int32(0), initialN, initialBias b, remaining := int32(0), int32(0) for _, r := range s { - if r < 0x80 { + if r < utf8.RuneSelf { b++ output = append(output, byte(r)) } else { diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 5546d37516..c7a58a61df 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -121,11 +121,11 @@ func dirList(w ResponseWriter, f File) { // Note that *os.File implements the io.ReadSeeker interface. func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) { sizeFunc := func() (int64, error) { - size, err := content.Seek(0, os.SEEK_END) + size, err := content.Seek(0, io.SeekEnd) if err != nil { return 0, errSeeker } - _, err = content.Seek(0, os.SEEK_SET) + _, err = content.Seek(0, io.SeekStart) if err != nil { return 0, errSeeker } @@ -166,7 +166,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, var buf [sniffLen]byte n, _ := io.ReadFull(content, buf[:]) ctype = DetectContentType(buf[:n]) - _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file + _, err := content.Seek(0, io.SeekStart) // rewind to output whole file if err != nil { Error(w, "seeker can't seek", StatusInternalServerError) return @@ -213,7 +213,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, // A response to a request for a single range MUST NOT // be sent using the multipart/byteranges media type." ra := ranges[0] - if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { + if _, err := content.Seek(ra.start, io.SeekStart); err != nil { Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) return } @@ -236,7 +236,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, pw.CloseWithError(err) return } - if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { + if _, err := content.Seek(ra.start, io.SeekStart); err != nil { pw.CloseWithError(err) return } diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go index 9253ebe43a..c811891e87 100644 --- a/src/net/http/fs_test.go +++ b/src/net/http/fs_test.go @@ -978,9 +978,9 @@ func TestLinuxSendfile(t *testing.T) { syscalls := "sendfile,sendfile64" switch runtime.GOARCH { - case "mips64", "mips64le": - // mips64 strace doesn't support sendfile64 and will error out - // if we specify that with `-e trace='. + case "mips64", "mips64le", "s390x": + // strace on the above platforms doesn't support sendfile64 + // and will error out if we specify that with `-e trace='. syscalls = "sendfile" } diff --git a/src/net/http/http.go b/src/net/http/http.go index a40b23dfdb..a121628632 100644 --- a/src/net/http/http.go +++ b/src/net/http/http.go @@ -4,9 +4,34 @@ package http +import ( + "strings" +) + // maxInt64 is the effective "infinite" value for the Server and // Transport's byte-limiting readers. const maxInt64 = 1<<63 - 1 // TODO(bradfitz): move common stuff here. The other files have accumulated // generic http stuff in random places. + +// contextKey is a value for use with context.WithValue. It's used as +// a pointer so it fits in an interface{} without allocation. +type contextKey struct { + name string +} + +func (k *contextKey) String() string { return "net/http context value " + k.name } + +// Given a string of the form "host", "host:port", or "[ipv6::address]:port", +// return true if the string includes a port. +func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } + +// removeEmptyPort strips the empty port in ":port" to "" +// as mandated by RFC 3986 Section 6.2.3. +func removeEmptyPort(host string) string { + if hasPort(host) { + return strings.TrimSuffix(host, ":") + } + return host +} diff --git a/src/net/http/httptest/httptest_test.go b/src/net/http/httptest/httptest_test.go index 18ba73880e..4f9ecbd8bb 100644 --- a/src/net/http/httptest/httptest_test.go +++ b/src/net/http/httptest/httptest_test.go @@ -155,10 +155,10 @@ func TestNewRequest(t *testing.T) { got := NewRequest(tt.method, tt.uri, tt.body) slurp, err := ioutil.ReadAll(got.Body) if err != nil { - t.Errorf("%i. ReadAll: %v", i, err) + t.Errorf("%d. ReadAll: %v", i, err) } if string(slurp) != tt.wantBody { - t.Errorf("%i. Body = %q; want %q", i, slurp, tt.wantBody) + t.Errorf("%d. Body = %q; want %q", i, slurp, tt.wantBody) } got.Body = nil // before DeepEqual if !reflect.DeepEqual(got.URL, tt.want.URL) { diff --git a/src/net/http/httputil/dump.go b/src/net/http/httputil/dump.go index ddde11a0e4..692ab62c9b 100644 --- a/src/net/http/httputil/dump.go +++ b/src/net/http/httputil/dump.go @@ -163,7 +163,6 @@ func valueOrDefault(value, def string) string { var reqWriteExcludeHeaderDump = map[string]bool{ "Host": true, // not in Header map anyway - "Content-Length": true, "Transfer-Encoding": true, "Trailer": true, } diff --git a/src/net/http/httputil/dump_test.go b/src/net/http/httputil/dump_test.go index fc884347a6..2e980d39f8 100644 --- a/src/net/http/httputil/dump_test.go +++ b/src/net/http/httputil/dump_test.go @@ -122,6 +122,10 @@ var dumpTests = []dumpTest{ Host: "post.tld", Path: "/", }, + Header: http.Header{ + "Content-Length": []string{"8193"}, + }, + ContentLength: 8193, ProtoMajor: 1, ProtoMinor: 1, @@ -135,6 +139,10 @@ var dumpTests = []dumpTest{ "Content-Length: 8193\r\n" + "Accept-Encoding: gzip\r\n\r\n" + strings.Repeat("a", 8193), + WantDump: "POST / HTTP/1.1\r\n" + + "Host: post.tld\r\n" + + "Content-Length: 8193\r\n\r\n" + + strings.Repeat("a", 8193), }, { @@ -144,6 +152,38 @@ var dumpTests = []dumpTest{ WantDump: "GET http://foo.com/ HTTP/1.1\r\n" + "User-Agent: blah\r\n\r\n", }, + + // Issue #7215. DumpRequest should return the "Content-Length" when set + { + Req: *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"), + 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 + { + Req: *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"), + 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 + { + Req: *mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + + "Host: passport.myhost.com\r\n" + + "\r\nkey1=name1&key2=name2"), + WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + + "Host: passport.myhost.com\r\n\r\n", + }, } func TestDumpRequest(t *testing.T) { diff --git a/src/net/http/httputil/example_test.go b/src/net/http/httputil/example_test.go index f856135742..6191603674 100644 --- a/src/net/http/httputil/example_test.go +++ b/src/net/http/httputil/example_test.go @@ -47,7 +47,7 @@ func ExampleDumpRequest() { fmt.Printf("%s", b) // Output: - // "POST / HTTP/1.1\r\nHost: www.example.org\r\nAccept-Encoding: gzip\r\nUser-Agent: Go-http-client/1.1\r\n\r\nGo is a general-purpose language designed with systems programming in mind." + // "POST / HTTP/1.1\r\nHost: www.example.org\r\nAccept-Encoding: gzip\r\nContent-Length: 75\r\nUser-Agent: Go-http-client/1.1\r\n\r\nGo is a general-purpose language designed with systems programming in mind." } func ExampleDumpRequestOut() { diff --git a/src/net/http/httputil/persist.go b/src/net/http/httputil/persist.go index 7874da3bec..51486e78e2 100644 --- a/src/net/http/httputil/persist.go +++ b/src/net/http/httputil/persist.go @@ -28,7 +28,7 @@ var errClosed = errors.New("i/o operation on closed connection") // Is is low-level, old, and unused by Go's current HTTP stack. // We should have deleted it before Go 1. // -// Deprecated: use the Server in package net/http instead. +// Deprecated: Use the Server in package net/http instead. type ServerConn struct { mu sync.Mutex // read-write protects the following fields c net.Conn @@ -45,7 +45,7 @@ type ServerConn struct { // Is is low-level, old, and unused by Go's current HTTP stack. // We should have deleted it before Go 1. // -// Deprecated: use the Server in package net/http instead. +// Deprecated: Use the Server in package net/http instead. func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn { if r == nil { r = bufio.NewReader(c) @@ -221,7 +221,7 @@ func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error { // Is is low-level, old, and unused by Go's current HTTP stack. // We should have deleted it before Go 1. // -// Deprecated: use Client or Transport in package net/http instead. +// Deprecated: Use Client or Transport in package net/http instead. type ClientConn struct { mu sync.Mutex // read-write protects the following fields c net.Conn @@ -239,7 +239,7 @@ type ClientConn struct { // Is is low-level, old, and unused by Go's current HTTP stack. // We should have deleted it before Go 1. // -// Deprecated: use the Client or Transport in package net/http instead. +// Deprecated: Use the Client or Transport in package net/http instead. func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn { if r == nil { r = bufio.NewReader(c) @@ -256,7 +256,7 @@ func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn { // Is is low-level, old, and unused by Go's current HTTP stack. // We should have deleted it before Go 1. // -// Deprecated: use the Client or Transport in package net/http instead. +// Deprecated: Use the Client or Transport in package net/http instead. func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn { cc := NewClientConn(c, r) cc.writeReq = (*http.Request).WriteProxy diff --git a/src/net/http/main_test.go b/src/net/http/main_test.go index 299cd7b2d2..d10fd89b54 100644 --- a/src/net/http/main_test.go +++ b/src/net/http/main_test.go @@ -5,7 +5,6 @@ package http_test import ( - "flag" "fmt" "net/http" "os" @@ -16,8 +15,6 @@ import ( "time" ) -var flaky = flag.Bool("flaky", false, "run known-flaky tests too") - func TestMain(m *testing.M) { v := m.Run() if v == 0 && goroutineLeaked() { @@ -91,12 +88,6 @@ func setParallel(t *testing.T) { } } -func setFlaky(t *testing.T, issue int) { - if !*flaky { - t.Skipf("skipping known flaky test; see golang.org/issue/%d", issue) - } -} - func afterTest(t testing.TB) { http.DefaultTransport.(*http.Transport).CloseIdleConnections() if testing.Short() { @@ -129,3 +120,17 @@ func afterTest(t testing.TB) { } t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks) } + +// waitCondition reports whether fn eventually returned true, +// checking immediately and then every checkEvery amount, +// until waitFor has elpased, at which point it returns false. +func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool { + deadline := time.Now().Add(waitFor) + for time.Now().Before(deadline) { + if fn() { + return true + } + time.Sleep(checkEvery) + } + return false +} diff --git a/src/net/http/pprof/pprof.go b/src/net/http/pprof/pprof.go index 2357d8ed1e..cb4086b963 100644 --- a/src/net/http/pprof/pprof.go +++ b/src/net/http/pprof/pprof.go @@ -30,7 +30,8 @@ // // go tool pprof http://localhost:6060/debug/pprof/profile // -// Or to look at the goroutine blocking profile: +// Or to look at the goroutine blocking profile, after calling +// runtime.SetBlockProfileRate in your program: // // go tool pprof http://localhost:6060/debug/pprof/block // @@ -119,8 +120,8 @@ func Profile(w http.ResponseWriter, r *http.Request) { // Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified. // The package initialization registers it as /debug/pprof/trace. func Trace(w http.ResponseWriter, r *http.Request) { - sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64) - if sec == 0 { + sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64) + if sec <= 0 || err != nil { sec = 1 } @@ -135,7 +136,7 @@ func Trace(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Could not enable tracing: %s\n", err) return } - sleep(w, time.Duration(sec)*time.Second) + sleep(w, time.Duration(sec*float64(time.Second))) trace.Stop() } diff --git a/src/net/http/request.go b/src/net/http/request.go index 371d36b097..a49ab36964 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -249,7 +249,7 @@ type Request struct { // // For server requests, this field is not applicable. // - // Deprecated: use the Context and WithContext methods + // Deprecated: Use the Context and WithContext methods // instead. If a Request's Cancel field and context are both // set, it is undefined whether Cancel is respected. Cancel <-chan struct{} @@ -266,9 +266,13 @@ type Request struct { // // The returned context is always non-nil; it defaults to the // background context. +// +// For outgoing client requests, the context controls cancelation. +// +// For incoming server requests, the context is canceled when either +// the client's connection closes, or when the ServeHTTP method +// returns. func (r *Request) Context() context.Context { - // TODO(bradfitz): document above what Context means for server and client - // requests, once implemented. if r.ctx != nil { return r.ctx } @@ -656,6 +660,8 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { if !ok && body != nil { rc = ioutil.NopCloser(body) } + // The host's colon:port should be normalized. See Issue 14836. + u.Host = removeEmptyPort(u.Host) req := &Request{ Method: method, URL: u, @@ -813,7 +819,7 @@ func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err erro } req.Header = Header(mimeHeader) - // RFC2616: Must treat + // RFC 2616: Must treat // GET /index.html HTTP/1.1 // Host: www.google.com // and diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go index ff4837f2fa..82c7af3cda 100644 --- a/src/net/http/request_test.go +++ b/src/net/http/request_test.go @@ -398,11 +398,13 @@ var newRequestHostTests = []struct { {"http://192.168.0.1/", "192.168.0.1"}, {"http://192.168.0.1:8080/", "192.168.0.1:8080"}, + {"http://192.168.0.1:/", "192.168.0.1"}, {"http://[fe80::1]/", "[fe80::1]"}, {"http://[fe80::1]:8080/", "[fe80::1]:8080"}, {"http://[fe80::1%25en0]/", "[fe80::1%en0]"}, {"http://[fe80::1%25en0]:8080/", "[fe80::1%en0]:8080"}, + {"http://[fe80::1%25en0]:/", "[fe80::1%en0]"}, } func TestNewRequestHost(t *testing.T) { diff --git a/src/net/http/response.go b/src/net/http/response.go index b49b77d8b9..91d4ffb7ec 100644 --- a/src/net/http/response.go +++ b/src/net/http/response.go @@ -185,7 +185,7 @@ func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) { return resp, nil } -// RFC2616: Should treat +// RFC 2616: Should treat // Pragma: no-cache // like // Cache-Control: no-cache diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index 638ba5f48f..5f206b1873 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -9,6 +9,7 @@ package http_test import ( "bufio" "bytes" + "context" "crypto/tls" "errors" "fmt" @@ -3989,6 +3990,89 @@ func TestServerValidatesHeaders(t *testing.T) { } } +func TestServerRequestContextCancel_ServeHTTPDone(t *testing.T) { + defer afterTest(t) + ctxc := make(chan context.Context, 1) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ctx := r.Context() + select { + case <-ctx.Done(): + t.Error("should not be Done in ServeHTTP") + default: + } + ctxc <- ctx + })) + defer ts.Close() + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + res.Body.Close() + ctx := <-ctxc + select { + case <-ctx.Done(): + default: + t.Error("context should be done after ServeHTTP completes") + } +} + +func TestServerRequestContextCancel_ConnClose(t *testing.T) { + // Currently the context is not canceled when the connection + // is closed because we're not reading from the connection + // until after ServeHTTP for the previous handler is done. + // Until the server code is modified to always be in a read + // (Issue 15224), this test doesn't work yet. + t.Skip("TODO(bradfitz): this test doesn't yet work; golang.org/issue/15224") + defer afterTest(t) + inHandler := make(chan struct{}) + handlerDone := make(chan struct{}) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + close(inHandler) + select { + case <-r.Context().Done(): + case <-time.After(3 * time.Second): + t.Errorf("timeout waiting for context to be done") + } + close(handlerDone) + })) + defer ts.Close() + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n") + select { + case <-inHandler: + case <-time.After(3 * time.Second): + t.Fatalf("timeout waiting to see ServeHTTP get called") + } + c.Close() // this should trigger the context being done + + select { + case <-handlerDone: + case <-time.After(3 * time.Second): + t.Fatalf("timeout waiting to see ServeHTTP exit") + } +} + +func TestServerContext_ServerContextKey(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ctx := r.Context() + got := ctx.Value(ServerContextKey) + if _, ok := got.(*Server); !ok { + t.Errorf("context value = %T; want *http.Server") + } + })) + defer ts.Close() + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + res.Body.Close() +} + func BenchmarkClientServer(b *testing.B) { b.ReportAllocs() b.StopTimer() @@ -4200,7 +4284,7 @@ func BenchmarkClient(b *testing.B) { if err != nil { b.Fatalf("ReadAll: %v", err) } - if bytes.Compare(body, data) != 0 { + if !bytes.Equal(body, data) { b.Fatalf("Got body: %q", body) } } diff --git a/src/net/http/server.go b/src/net/http/server.go index a2ef0ddf20..da17fccbae 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -9,6 +9,7 @@ package http import ( "bufio" "bytes" + "context" "crypto/tls" "errors" "fmt" @@ -50,6 +51,9 @@ var ( // ResponseWriter. Cautious handlers should read the Request.Body // first, and then reply. // +// Except for reading the body, handlers should not modify the +// provided Request. +// // If ServeHTTP panics, the server (the caller of ServeHTTP) assumes // that the effect of the panic was isolated to the active request. // It recovers the panic, logs a stack trace to the server error log, @@ -90,6 +94,10 @@ type ResponseWriter interface { // The Flusher interface is implemented by ResponseWriters that allow // an HTTP handler to flush buffered data to the client. // +// The default HTTP/1.x and HTTP/2 ResponseWriter implementations +// support Flusher, but ResponseWriter wrappers may not. Handlers +// should always test for this ability at runtime. +// // Note that even for ResponseWriters that support Flush, // if the client is connected through an HTTP proxy, // the buffered data may not reach the client until the response @@ -101,6 +109,11 @@ type Flusher interface { // The Hijacker interface is implemented by ResponseWriters that allow // an HTTP handler to take over the connection. +// +// The default ResponseWriter for HTTP/1.x connections supports +// Hijacker, but HTTP/2 connections intentionally do not. +// ResponseWriter wrappers may also not support Hijacker. Handlers +// should always test for this ability at runtime. type Hijacker interface { // Hijack lets the caller take over the connection. // After a call to Hijack(), the HTTP server library @@ -143,6 +156,14 @@ type CloseNotifier interface { CloseNotify() <-chan bool } +var ( + // ServerContextKey is a context key. It can be used in HTTP + // handlers with context.WithValue to access the server that + // started the handler. The associated value will be of + // type *Server. + ServerContextKey = &contextKey{"http-server"} +) + // A conn represents the server side of an HTTP connection. type conn struct { // server is the server on which the connection arrived. @@ -306,11 +327,14 @@ func (cw *chunkWriter) close() { // A response represents the server side of an HTTP response. type response struct { - conn *conn - req *Request // request for this response - reqBody io.ReadCloser - wroteHeader bool // reply header has been (logically) written - wroteContinue bool // 100 Continue response was written + conn *conn + req *Request // request for this response + reqBody io.ReadCloser + cancelCtx context.CancelFunc // when ServeHTTP exits + wroteHeader bool // reply header has been (logically) written + wroteContinue bool // 100 Continue response was written + wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive" + wantsClose bool // HTTP request has Connection "close" w *bufio.Writer // buffers output in chunks to chunkWriter cw chunkWriter @@ -681,7 +705,7 @@ func appendTime(b []byte, t time.Time) []byte { var errTooLarge = errors.New("http: request too large") // Read next request from connection. -func (c *conn) readRequest() (w *response, err error) { +func (c *conn) readRequest(ctx context.Context) (w *response, err error) { if c.hijacked() { return nil, ErrHijacked } @@ -710,6 +734,10 @@ func (c *conn) readRequest() (w *response, err error) { } return nil, err } + + ctx, cancelCtx := context.WithCancel(ctx) + req.ctx = ctx + c.lastMethod = req.Method c.r.setInfiniteReadLimit() @@ -744,10 +772,17 @@ func (c *conn) readRequest() (w *response, err error) { w = &response{ conn: c, + cancelCtx: cancelCtx, req: req, reqBody: req.Body, handlerHeader: make(Header), contentLength: -1, + + // We populate these ahead of time so we're not + // reading from req.Header after their Handler starts + // and maybe mutates it (Issue 14940) + wants10KeepAlive: req.wantsHttp10KeepAlive(), + wantsClose: req.wantsClose(), } if isH2Upgrade { w.closeAfterReply = true @@ -929,7 +964,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { // If this was an HTTP/1.0 request with keep-alive and we sent a // Content-Length back, we can make this a keep-alive response ... - if w.req.wantsHttp10KeepAlive() && keepAlivesEnabled { + if w.wants10KeepAlive && keepAlivesEnabled { sentLength := header.get("Content-Length") != "" if sentLength && header.get("Connection") == "keep-alive" { w.closeAfterReply = false @@ -939,12 +974,12 @@ func (cw *chunkWriter) writeHeader(p []byte) { // Check for a explicit (and valid) Content-Length header. hasCL := w.contentLength != -1 - if w.req.wantsHttp10KeepAlive() && (isHEAD || hasCL) { + if w.wants10KeepAlive && (isHEAD || hasCL) { _, connectionHeaderSet := header["Connection"] if !connectionHeaderSet { setHeader.connection = "keep-alive" } - } else if !w.req.ProtoAtLeast(1, 1) || w.req.wantsClose() { + } else if !w.req.ProtoAtLeast(1, 1) || w.wantsClose { w.closeAfterReply = true } @@ -1384,7 +1419,7 @@ type badRequestError string func (e badRequestError) Error() string { return "Bad Request: " + string(e) } // Serve a new connection. -func (c *conn) serve() { +func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() defer func() { if err := recover(); err != nil { @@ -1421,12 +1456,17 @@ func (c *conn) serve() { } } + // HTTP/1.x from here on. + c.r = &connReader{r: c.rwc} c.bufr = newBufioReader(c.r) c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) + ctx, cancelCtx := context.WithCancel(ctx) + defer cancelCtx() + for { - w, err := c.readRequest() + w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) @@ -1474,6 +1514,7 @@ func (c *conn) serve() { // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. serverHandler{c.server}.ServeHTTP(w, w.req) + w.cancelCtx() if c.hijacked() { return } @@ -1625,6 +1666,8 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { // Helper handlers // Error replies to the request with the specified error message and HTTP code. +// It does not otherwise end the request; the caller should ensure no further +// writes are done to w. // The error message should be plain text. func Error(w ResponseWriter, error string, code int) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") @@ -1713,7 +1756,7 @@ func Redirect(w ResponseWriter, r *Request, urlStr string, code int) { w.Header().Set("Location", urlStr) w.WriteHeader(code) - // RFC2616 recommends that a short note "SHOULD" be included in the + // RFC 2616 recommends that a short note "SHOULD" be included in the // response because older user agents may not understand 301/307. // Shouldn't send the response for POST or HEAD; that leaves GET. if r.Method == "GET" { @@ -2122,6 +2165,10 @@ func (srv *Server) Serve(l net.Listener) error { if err := srv.setupHTTP2(); err != nil { return err } + // TODO: allow changing base context? can't imagine concrete + // use cases yet. + baseCtx := context.Background() + ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, e := l.Accept() if e != nil { @@ -2143,7 +2190,7 @@ func (srv *Server) Serve(l net.Listener) error { tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return - go c.serve() + go c.serve(ctx) } } diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 4c130f0cc4..501e4be08c 100644 --- a/src/net/http/transfer.go +++ b/src/net/http/transfer.go @@ -276,7 +276,7 @@ func (t *transferReader) protoAtLeast(m, n int) bool { } // bodyAllowedForStatus reports whether a given response status code -// permits a body. See RFC2616, section 4.4. +// permits a body. See RFC 2616, section 4.4. func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: @@ -368,7 +368,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { // If there is no Content-Length or chunked Transfer-Encoding on a *Response // and the status is not 1xx, 204 or 304, then the body is unbounded. - // See RFC2616, section 4.4. + // See RFC 2616, section 4.4. switch msg.(type) { case *Response: if realLength == -1 && diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 7692abff47..3ccc6dd0df 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -12,6 +12,7 @@ package http import ( "bufio" "compress/gzip" + "context" "crypto/tls" "errors" "fmt" @@ -32,10 +33,10 @@ import ( // $no_proxy) environment variables. var DefaultTransport RoundTripper = &Transport{ Proxy: ProxyFromEnvironment, - Dial: (&net.Dialer{ + Dialer: &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - }).Dial, + }, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } @@ -64,6 +65,7 @@ const DefaultMaxIdleConnsPerHost = 2 type Transport struct { idleMu sync.Mutex wantIdle bool // user has requested to close all idle conns + idleCount int idleConn map[connectMethodKey][]*persistConn idleConnCh map[connectMethodKey]chan *persistConn @@ -80,10 +82,17 @@ type Transport struct { Proxy func(*Request) (*url.URL, error) // Dial specifies the dial function for creating unencrypted - // TCP connections. - // If Dial is nil, net.Dial is used. + // TCP connections. If Dial and Dialer are both nil, net.Dial + // is used. + // + // Deprecated: Use Dialer instead. If both are specified, Dialer + // takes precedence. Dial func(network, addr string) (net.Conn, error) + // Dialer optionally specifies a dialer configuration to use + // for new connections. + Dialer *net.Dialer + // DialTLS specifies an optional dial function for creating // TLS connections for non-proxied HTTPS requests. // @@ -158,7 +167,7 @@ type Transport struct { nextProtoOnce sync.Once h2transport *http2Transport // non-nil if http2 wired up - // TODO: tunable on global max cached connections + // TODO: MaxIdleConns tunable for global max cached connections (Issue 15461) // TODO: tunable on timeout on cached connections // TODO: tunable on max per-host TCP dials in flight (Issue 13957) } @@ -605,6 +614,7 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error { } } t.idleConn[key] = append(t.idleConn[key], pconn) + t.idleCount++ return nil } @@ -630,13 +640,14 @@ func (t *Transport) getIdleConnCh(cm connectMethod) chan *persistConn { return ch } -func (t *Transport) getIdleConn(cm connectMethod) (pconn *persistConn) { +func (t *Transport) getIdleConn(cm connectMethod) *persistConn { key := cm.key() t.idleMu.Lock() defer t.idleMu.Unlock() if t.idleConn == nil { return nil } + var pconn *persistConn for { pconns, ok := t.idleConn[key] if !ok { @@ -651,8 +662,44 @@ func (t *Transport) getIdleConn(cm connectMethod) (pconn *persistConn) { pconn = pconns[len(pconns)-1] t.idleConn[key] = pconns[:len(pconns)-1] } - if !pconn.isBroken() { - return + t.idleCount-- + if pconn.isBroken() { + // There is a tiny window where this is + // possible, between the connecting dying and + // the persistConn readLoop calling + // Transport.removeIdleConn. Just skip it and + // carry on. + continue + } + return pconn + } +} + +// removeIdleConn marks pconn as dead. +func (t *Transport) removeIdleConn(pconn *persistConn) { + key := pconn.cacheKey + t.idleMu.Lock() + defer t.idleMu.Unlock() + + pconns, _ := t.idleConn[key] + switch len(pconns) { + case 0: + // Nothing + case 1: + if pconns[0] == pconn { + t.idleCount-- + delete(t.idleConn, key) + } + default: + // TODO(bradfitz): map into LRU element? + for i, v := range pconns { + if v != pconn { + continue + } + pconns[i] = pconns[len(pconns)-1] + t.idleConn[key] = pconns[:len(pconns)-1] + t.idleCount-- + break } } } @@ -689,7 +736,10 @@ func (t *Transport) replaceReqCanceler(r *Request, fn func()) bool { return true } -func (t *Transport) dial(network, addr string) (net.Conn, error) { +func (t *Transport) dial(ctx context.Context, network, addr string) (net.Conn, error) { + if t.Dialer != nil { + return t.Dialer.DialContext(ctx, network, addr) + } if t.Dial != nil { c, err := t.Dial(network, addr) if c == nil && err == nil { @@ -705,6 +755,7 @@ func (t *Transport) dial(network, addr string) (net.Conn, error) { // and/or setting up TLS. If this doesn't return an error, the persistConn // is ready to write requests to. func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error) { + ctx := req.Context() if pc := t.getIdleConn(cm); pc != nil { // set request canceler to some non-nil function so we // can detect whether it was cleared between now and when @@ -738,7 +789,7 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error t.setReqCanceler(req, func() { close(cancelc) }) go func() { - pc, err := t.dialConn(cm) + pc, err := t.dialConn(ctx, cm) dialc <- dialRes{pc, err} }() @@ -767,7 +818,7 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error } } -func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { +func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) { pconn := &persistConn{ t: t, cacheKey: cm.key(), @@ -797,7 +848,7 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { pconn.tlsState = &cs } } else { - conn, err := t.dial("tcp", cm.addr()) + conn, err := t.dial(ctx, "tcp", cm.addr()) if err != nil { if cm.proxyURL != nil { err = fmt.Errorf("http: error connecting to proxy %s: %v", cm.proxyURL, err) @@ -1108,7 +1159,10 @@ func (pc *persistConn) cancelRequest() { func (pc *persistConn) readLoop() { closeErr := errReadLoopExiting // default value, if not changed below - defer func() { pc.close(closeErr) }() + defer func() { + pc.close(closeErr) + pc.t.removeIdleConn(pc) + }() tryPutIdleConn := func() bool { if err := pc.t.tryPutIdleConn(pc); err != nil { diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 7a01dca394..2e27cc1850 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -18,6 +18,7 @@ import ( "crypto/tls" "errors" "fmt" + "internal/testenv" "io" "io/ioutil" "log" @@ -437,6 +438,54 @@ func TestTransportMaxPerHostIdleConns(t *testing.T) { } } +func TestTransportRemovesDeadIdleConnections(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + io.WriteString(w, r.RemoteAddr) + })) + defer ts.Close() + + tr := &Transport{} + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + + doReq := func(name string) string { + // Do a POST instead of a GET to prevent the Transport's + // idempotent request retry logic from kicking in... + res, err := c.Post(ts.URL, "", nil) + if err != nil { + t.Fatalf("%s: %v", name, err) + } + if res.StatusCode != 200 { + t.Fatalf("%s: %v", name, res.Status) + } + defer res.Body.Close() + slurp, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatalf("%s: %v", name, err) + } + return string(slurp) + } + + first := doReq("first") + keys1 := tr.IdleConnKeysForTesting() + + ts.CloseClientConnections() + + var keys2 []string + if !waitCondition(3*time.Second, 50*time.Millisecond, func() bool { + keys2 = tr.IdleConnKeysForTesting() + return len(keys2) == 0 + }) { + t.Fatalf("Transport didn't notice idle connection's death.\nbefore: %q\n after: %q\n", keys1, keys2) + } + + second := doReq("second") + if first == second { + t.Errorf("expected a different connection between requests. got %q both times", first) + } +} + func TestTransportServerClosingUnexpectedly(t *testing.T) { setParallel(t) defer afterTest(t) @@ -2229,7 +2278,7 @@ func TestTransportTLSHandshakeTimeout(t *testing.T) { // Trying to repro golang.org/issue/3514 func TestTLSServerClosesConnection(t *testing.T) { defer afterTest(t) - setFlaky(t, 7634) + testenv.SkipFlaky(t, 7634) closedc := make(chan bool, 1) ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
