From: Brad Fitzpatrick Date: Mon, 4 Apr 2016 20:31:08 +0000 (-0700) Subject: net/http: add Request.Context and Request.WithContext X-Git-Tag: go1.7beta1~908 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c1c7547f6ad7264c1d6eea3fc1645b2eab104694;p=gostls13.git net/http: add Request.Context and Request.WithContext Currently only used by the client. The server is not yet wired up. A TODO remains to document how it works server-side, once implemented. Updates #14660 Change-Id: I27c2e74198872b2720995fa8271d91de200e23d5 Reviewed-on: https://go-review.googlesource.com/21496 Reviewed-by: Andrew Gerrand --- diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 21e12d315e..c066048630 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -357,7 +357,8 @@ var pkgDeps = map[string][]string{ // HTTP, kingpin of dependencies. "net/http": { "L4", "NET", "OS", - "compress/gzip", "crypto/tls", "mime/multipart", "runtime/debug", + "context", "compress/gzip", "crypto/tls", + "mime/multipart", "runtime/debug", "net/http/internal", "golang.org/x/net/http2/hpack", }, diff --git a/src/net/http/request.go b/src/net/http/request.go index d9ebb26dfc..371d36b097 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -9,6 +9,7 @@ package http import ( "bufio" "bytes" + "context" "crypto/tls" "encoding/base64" "errors" @@ -247,7 +248,43 @@ type Request struct { // RoundTripper may support Cancel. // // For server requests, this field is not applicable. + // + // 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{} + + // ctx is either the client or server context. It should only + // be modified via copying the whole Request using WithContext. + // It is unexported to prevent people from using Context wrong + // and mutating the contexts held by callers of the same request. + ctx context.Context +} + +// Context returns the request's context. To change the context, use +// WithContext. +// +// The returned context is always non-nil; it defaults to the +// background context. +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 + } + return context.Background() +} + +// WithContext returns a shallow copy of r with its context changed +// to ctx. The provided ctx must be non-nil. +func (r *Request) WithContext(ctx context.Context) *Request { + if ctx == nil { + panic("nil context") + } + r2 := new(Request) + *r2 = *r + r2.ctx = ctx + return r2 } // ProtoAtLeast reports whether the HTTP protocol used diff --git a/src/net/http/response_test.go b/src/net/http/response_test.go index 354a21bc46..2591e3ac81 100644 --- a/src/net/http/response_test.go +++ b/src/net/http/response_test.go @@ -10,6 +10,7 @@ import ( "compress/gzip" "crypto/rand" "fmt" + "go/ast" "io" "io/ioutil" "net/http/internal" @@ -656,10 +657,14 @@ func diff(t *testing.T, prefix string, have, want interface{}) { t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) } for i := 0; i < hv.NumField(); i++ { + name := hv.Type().Field(i).Name + if !ast.IsExported(name) { + continue + } hf := hv.Field(i).Interface() wf := wv.Field(i).Interface() if !reflect.DeepEqual(hf, wf) { - t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf) + t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf) } } } diff --git a/src/net/http/transport.go b/src/net/http/transport.go index d1b64c7da9..7692abff47 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -758,6 +758,9 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error case <-req.Cancel: handlePendingDial() return nil, errRequestCanceledConn + case <-req.Context().Done(): + handlePendingDial() + return nil, errRequestCanceledConn case <-cancelc: handlePendingDial() return nil, errRequestCanceledConn @@ -1263,6 +1266,9 @@ func (pc *persistConn) readLoop() { case <-rc.req.Cancel: alive = false pc.t.CancelRequest(rc.req) + case <-rc.req.Context().Done(): + alive = false + pc.t.CancelRequest(rc.req) case <-pc.closech: alive = false } @@ -1567,6 +1573,9 @@ WaitResponse: case <-cancelChan: pc.t.CancelRequest(req.Request) cancelChan = nil + case <-req.Context().Done(): + pc.t.CancelRequest(req.Request) + cancelChan = nil } } diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 9c2e40d7f5..7a01dca394 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -13,6 +13,7 @@ import ( "bufio" "bytes" "compress/gzip" + "context" "crypto/rand" "crypto/tls" "errors" @@ -1625,7 +1626,13 @@ func TestCancelRequestWithChannel(t *testing.T) { } } -func TestCancelRequestWithChannelBeforeDo(t *testing.T) { +func TestCancelRequestWithChannelBeforeDo_Cancel(t *testing.T) { + testCancelRequestWithChannelBeforeDo(t, false) +} +func TestCancelRequestWithChannelBeforeDo_Context(t *testing.T) { + testCancelRequestWithChannelBeforeDo(t, true) +} +func testCancelRequestWithChannelBeforeDo(t *testing.T, withCtx bool) { setParallel(t) defer afterTest(t) unblockc := make(chan bool) @@ -1646,9 +1653,15 @@ func TestCancelRequestWithChannelBeforeDo(t *testing.T) { c := &Client{Transport: tr} req, _ := NewRequest("GET", ts.URL, nil) - ch := make(chan struct{}) - req.Cancel = ch - close(ch) + if withCtx { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + req = req.WithContext(ctx) + } else { + ch := make(chan struct{}) + req.Cancel = ch + close(ch) + } _, err := c.Do(req) if err == nil || !strings.Contains(err.Error(), "canceled") {