Skip to content

Commit

Permalink
net/http: add Request.Context and Request.WithContext
Browse files Browse the repository at this point in the history
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 <adg@golang.org>
  • Loading branch information
bradfitz committed Apr 5, 2016
1 parent fcac880 commit c1c7547
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/go/build/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
Expand Down
37 changes: 37 additions & 0 deletions src/net/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package http
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"errors"
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/net/http/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"compress/gzip"
"crypto/rand"
"fmt"
"go/ast"
"io"
"io/ioutil"
"net/http/internal"
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/net/http/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
}

Expand Down
21 changes: 17 additions & 4 deletions src/net/http/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"bufio"
"bytes"
"compress/gzip"
"context"
"crypto/rand"
"crypto/tls"
"errors"
Expand Down Expand Up @@ -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)
Expand All @@ -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") {
Expand Down

0 comments on commit c1c7547

Please sign in to comment.