"time"
)
+// h2DefaultTransport is the HTTP/2 version of DefaultTransport.
+// DefaultTransport and h2DefaultTransport are wired up together.
+var h2DefaultTransport = &http2Transport{}
+
// DefaultTransport is the default implementation of Transport and is
// used by DefaultClient. It establishes network connections as needed
// and caches them for reuse by subsequent calls. It uses HTTP proxies
ExpectContinueTimeout: 1 * time.Second,
}
+func init() {
+ // TODO(bradfitz,adg): remove the following line before Go 1.6
+ // ships. This just gives us a mechanism to temporarily
+ // enable the http2 client during development.
+ if !strings.Contains(os.Getenv("GODEBUG"), "h2client=1") {
+ return
+ }
+
+ t := DefaultTransport.(*Transport)
+
+ // TODO(bradfitz,adg): move all this up to DefaultTransport before Go 1.6:
+ t.RegisterProtocol("https", noDialH2Transport{h2DefaultTransport})
+ t.TLSClientConfig = &tls.Config{
+ NextProtos: []string{"h2"},
+ }
+ t.TLSNextProto = map[string]func(string, *tls.Conn) RoundTripper{
+ "h2": http2TransportForConn,
+ }
+}
+
+// noDialH2Transport is a RoundTripper which only tries to complete the request if
+// the wrapped *http2Transport already has a cached connection to the host.
+type noDialH2Transport struct{ rt *http2Transport }
+
+func (t noDialH2Transport) RoundTrip(req *Request) (*Response, error) {
+ // TODO(bradfitz): wire up http2.Transport
+ return nil, ErrSkipAltProtocol
+}
+
+func http2TransportForConn(authority string, c *tls.Conn) RoundTripper {
+ // TODO(bradfitz): donate c to h2DefaultTransport:
+ // h2DefaultTransport.AddIdleConn(authority, c)
+ return h2DefaultTransport
+}
+
// DefaultMaxIdleConnsPerHost is the default value of Transport's
// MaxIdleConnsPerHost.
const DefaultMaxIdleConnsPerHost = 2
// This time does not include the time to send the request header.
ExpectContinueTimeout time.Duration
+ // TLSNextProto specifies how the Transport switches to an
+ // alternate protocol (such as HTTP/2) after a TLS NPN/ALPN
+ // protocol negotiation. If Transport dials an TLS connection
+ // with a non-empty protocol name and TLSNextProto contains a
+ // map entry for that key (such as "h2"), then the func is
+ // called with the request's authority (such as "example.com"
+ // or "example.com:1234") and the TLS connection. The function
+ // must return a RoundTripper that then handles the request.
+ TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper
+
// TODO: tunable on global max cached connections
// TODO: tunable on timeout on cached connections
}
//
// For higher-level HTTP client support (such as handling of cookies
// and redirects), see Get, Post, and the Client type.
-func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
+func (t *Transport) RoundTrip(req *Request) (*Response, error) {
if req.URL == nil {
req.closeBody()
return nil, errors.New("http: nil Request.URL")
req.closeBody()
return nil, errors.New("http: nil Request.Header")
}
- if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
- t.altMu.RLock()
- var rt RoundTripper
- if t.altProto != nil {
- rt = t.altProto[req.URL.Scheme]
+ // TODO(bradfitz): switch to atomic.Value for this map instead of RWMutex
+ t.altMu.RLock()
+ altRT := t.altProto[req.URL.Scheme]
+ t.altMu.RUnlock()
+ if altRT != nil {
+ if resp, err := altRT.RoundTrip(req); err != ErrSkipAltProtocol {
+ return resp, err
}
- t.altMu.RUnlock()
- if rt == nil {
- req.closeBody()
- return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
- }
- return rt.RoundTrip(req)
+ }
+ if s := req.URL.Scheme; s != "http" && s != "https" {
+ req.closeBody()
+ return nil, &badStringError{"unsupported protocol scheme", s}
}
if req.URL.Host == "" {
req.closeBody()
req.closeBody()
return nil, err
}
-
+ if pconn.alt != nil {
+ // HTTP/2 path.
+ return pconn.alt.RoundTrip(req)
+ }
return pconn.roundTrip(treq)
}
+// ErrSkipAltProtocol is a sentinel error value defined by Transport.RegisterProtocol.
+var ErrSkipAltProtocol = errors.New("net/http: skip alternate protocol")
+
// RegisterProtocol registers a new protocol with scheme.
// The Transport will pass requests using the given scheme to rt.
// It is rt's responsibility to simulate HTTP request semantics.
//
// RegisterProtocol can be used by other packages to provide
// implementations of protocol schemes like "ftp" or "file".
+//
+// If rt.RoundTrip returns ErrSkipAltProtocol, the Transport will
+// handle the RoundTrip itself for that one request, as if the
+// protocol were not registered.
func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) {
- if scheme == "http" || scheme == "https" {
- panic("protocol " + scheme + " already registered")
- }
t.altMu.Lock()
defer t.altMu.Unlock()
if t.altProto == nil {
pconn.conn = tlsConn
}
+ if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" {
+ if next, ok := t.TLSNextProto[s.NegotiatedProtocol]; ok {
+ return &persistConn{alt: next(cm.targetAddr, pconn.conn.(*tls.Conn))}, nil
+ }
+ }
+
pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF})
pconn.bw = bufio.NewWriter(pconn.conn)
go pconn.readLoop()
// persistConn wraps a connection, usually a persistent one
// (but may be used for non-keep-alive requests as well)
type persistConn struct {
+ // alt optionally specifies the TLS NextProto RoundTripper.
+ // This is used for HTTP/2 today and future protocol laters.
+ // If it's non-nil, the rest of the fields are unused.
+ alt RoundTripper
+
t *Transport
cacheKey connectMethodKey
conn net.Conn