aboutsummaryrefslogtreecommitdiff
path: root/src/net/http
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/http')
-rw-r--r--src/net/http/client.go5
-rw-r--r--src/net/http/client_test.go28
-rw-r--r--src/net/http/clientserver_test.go28
-rw-r--r--src/net/http/cookiejar/punycode.go2
-rw-r--r--src/net/http/fs.go10
-rw-r--r--src/net/http/fs_test.go6
-rw-r--r--src/net/http/http.go25
-rw-r--r--src/net/http/httptest/httptest_test.go4
-rw-r--r--src/net/http/httputil/dump.go1
-rw-r--r--src/net/http/httputil/dump_test.go40
-rw-r--r--src/net/http/httputil/example_test.go2
-rw-r--r--src/net/http/httputil/persist.go10
-rw-r--r--src/net/http/main_test.go23
-rw-r--r--src/net/http/pprof/pprof.go9
-rw-r--r--src/net/http/request.go14
-rw-r--r--src/net/http/request_test.go2
-rw-r--r--src/net/http/response.go2
-rw-r--r--src/net/http/serve_test.go86
-rw-r--r--src/net/http/server.go73
-rw-r--r--src/net/http/transfer.go4
-rw-r--r--src/net/http/transport.go80
-rw-r--r--src/net/http/transport_test.go51
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) {