git » go-net » commit 96dbb96

http2: make Transport use Request.Context, set Response.Uncompressed

author Brad Fitzpatrick
2016-05-10 16:41:55 UTC
committer Brad Fitzpatrick
2016-05-10 17:26:03 UTC
parent 2a35e686583654a1b89ca79c4ac78cb3d6529ca3

http2: make Transport use Request.Context, set Response.Uncompressed

Go 1.7 introduces Request.Context and Response.Uncompressed. Use them
both in the http2 Transport when building for Go 1.7.

Tests are in the main repo.

Updates golang/go#15366
Updates golang/go#15134

Change-Id: I4b78a2710b6cf30c769c7f85cea15ecde653a317
Reviewed-on: https://go-review.googlesource.com/23002
Reviewed-by: Andrew Gerrand <adg@golang.org>

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
 }