author | Brad Fitzpatrick
<bradfitz@golang.org> 2016-05-10 16:41:55 UTC |
committer | Brad Fitzpatrick
<bradfitz@golang.org> 2016-05-10 17:26:03 UTC |
parent | 2a35e686583654a1b89ca79c4ac78cb3d6529ca3 |
http2/go17.go | +16 | -0 |
http2/not_go17.go | +22 | -0 |
http2/transport.go | +22 | -8 |
diff --git a/http2/go17.go b/http2/go17.go new file mode 100644 index 0000000..5f6238c --- /dev/null +++ b/http2/go17.go @@ -0,0 +1,16 @@ +// Copyright 2016 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. + +// +build go1.7 + +package http2 + +import ( + "context" + "net/http" +) + +func reqContext(r *http.Request) context.Context { return r.Context() } + +func setResponseUncompressed(res *http.Response) { res.Uncompressed = true } diff --git a/http2/not_go17.go b/http2/not_go17.go new file mode 100644 index 0000000..c7a4d30 --- /dev/null +++ b/http2/not_go17.go @@ -0,0 +1,22 @@ +// Copyright 2016 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. + +// +build !go1.7 + +package http2 + +import "net/http" + +type fakeContext struct{} + +func (fakeContext) Done() <-chan struct{} { return nil } +func (fakeContext) Err() error { panic("should not be called") } + +func reqContext(r *http.Request) fakeContext { + return fakeContext{} +} + +func setResponseUncompressed(res *http.Response) { + // Nothing. +} diff --git a/http2/transport.go b/http2/transport.go index 3284faa..a7ada8f 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -195,18 +195,22 @@ type clientStream struct { } // awaitRequestCancel runs in its own goroutine and waits for the user -// to either cancel a RoundTrip request (using the provided -// Request.Cancel channel), or for the request to be done (any way it -// might be removed from the cc.streams map: peer reset, successful -// completion, TCP connection breakage, etc) -func (cs *clientStream) awaitRequestCancel(cancel <-chan struct{}) { - if cancel == nil { +// to cancel a RoundTrip request, its context to expire, or for the +// request to be done (any way it might be removed from the cc.streams +// map: peer reset, successful completion, TCP connection breakage, +// etc) +func (cs *clientStream) awaitRequestCancel(req *http.Request) { + ctx := reqContext(req) + if req.Cancel == nil && ctx.Done() == nil { return } select { - case <-cancel: + case <-req.Cancel: cs.bufPipe.CloseWithError(errRequestCanceled) cs.cc.writeStreamReset(cs.ID, ErrCodeCancel, nil) + case <-ctx.Done(): + cs.bufPipe.CloseWithError(ctx.Err()) + cs.cc.writeStreamReset(cs.ID, ErrCodeCancel, nil) case <-cs.done: } } @@ -684,6 +688,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { readLoopResCh := cs.resc bodyWritten := false + ctx := reqContext(req) for { select { @@ -716,6 +721,14 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel) } return nil, errTimeout + case <-ctx.Done(): + cc.forgetStreamID(cs.ID) + if !hasBody || bodyWritten { + cc.writeStreamReset(cs.ID, ErrCodeCancel, nil) + } else { + cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel) + } + return nil, ctx.Err() case <-req.Cancel: cc.forgetStreamID(cs.ID) if !hasBody || bodyWritten { @@ -1284,13 +1297,14 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra cs.bufPipe = pipe{b: buf} cs.bytesRemain = res.ContentLength res.Body = transportResponseBody{cs} - go cs.awaitRequestCancel(cs.req.Cancel) + go cs.awaitRequestCancel(cs.req) if cs.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" { res.Header.Del("Content-Encoding") res.Header.Del("Content-Length") res.ContentLength = -1 res.Body = &gzipReader{body: res.Body} + setResponseUncompressed(res) } return res, nil }