Browse Source

http2: allow http scheme for http2

The existing transport implementation limit scheme to https, so
simple h2c can not be used, if we loose the limit to http scheme,
it works well with existing h2c server support.

Fixes golang/go#15830

Change-Id: Icfe65d72b3e6fb41f69019539fb5b86144814378
Reviewed-on: https://go-review.googlesource.com/23181
Reviewed-by: Andrew Gerrand <adg@golang.org>
Run-TryBot: Andrew Gerrand <adg@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
ayanamist 9 years ago
parent
commit
b3e9c8fbe0
3 changed files with 58 additions and 5 deletions
  1. 1 1
      http2/configure_transport.go
  2. 12 4
      http2/transport.go
  3. 45 0
      http2/transport_test.go

+ 1 - 1
http2/configure_transport.go

@@ -32,7 +32,7 @@ func configureTransport(t1 *http.Transport) (*Transport, error) {
 		t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
 		t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
 	}
 	}
 	upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
 	upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
-		addr := authorityAddr(authority)
+		addr := authorityAddr("https", authority)
 		if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
 		if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
 			go c.Close()
 			go c.Close()
 			return erringRoundTripper{err}
 			return erringRoundTripper{err}

+ 12 - 4
http2/transport.go

@@ -77,6 +77,10 @@ type Transport struct {
 	// uncompressed.
 	// uncompressed.
 	DisableCompression bool
 	DisableCompression bool
 
 
+	// AllowHTTP, if true, permits HTTP/2 requests using the insecure,
+	// plain-text "http" scheme. Note that this does not enable h2c support.
+	AllowHTTP bool
+
 	// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
 	// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
 	// send in the initial settings frame. It is how many bytes
 	// send in the initial settings frame. It is how many bytes
 	// of response headers are allow. Unlike the http2 spec, zero here
 	// of response headers are allow. Unlike the http2 spec, zero here
@@ -275,20 +279,24 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
 
 
 // authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
 // authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
 // and returns a host:port. The port 443 is added if needed.
 // and returns a host:port. The port 443 is added if needed.
-func authorityAddr(authority string) (addr string) {
+func authorityAddr(scheme string, authority string) (addr string) {
 	if _, _, err := net.SplitHostPort(authority); err == nil {
 	if _, _, err := net.SplitHostPort(authority); err == nil {
 		return authority
 		return authority
 	}
 	}
-	return net.JoinHostPort(authority, "443")
+	port := "443"
+	if scheme == "http" {
+		port = "80"
+	}
+	return net.JoinHostPort(authority, port)
 }
 }
 
 
 // RoundTripOpt is like RoundTrip, but takes options.
 // RoundTripOpt is like RoundTrip, but takes options.
 func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
 func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
-	if req.URL.Scheme != "https" {
+	if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
 		return nil, errors.New("http2: unsupported scheme")
 		return nil, errors.New("http2: unsupported scheme")
 	}
 	}
 
 
-	addr := authorityAddr(req.URL.Host)
+	addr := authorityAddr(req.URL.Scheme, req.URL.Host)
 	for {
 	for {
 		cc, err := t.connPool().GetClientConn(req, addr)
 		cc, err := t.connPool().GetClientConn(req, addr)
 		if err != nil {
 		if err != nil {

+ 45 - 0
http2/transport_test.go

@@ -53,6 +53,51 @@ func TestTransportExternal(t *testing.T) {
 	res.Write(os.Stdout)
 	res.Write(os.Stdout)
 }
 }
 
 
+func startH2cServer(t *testing.T) net.Listener {
+	h2Server := &Server{}
+	l := newLocalListener(t)
+	go func() {
+		conn, err := l.Accept()
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		h2Server.ServeConn(conn, &ServeConnOpts{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			fmt.Fprintf(w, "Hello, %v", r.URL.Path)
+		})})
+	}()
+	return l
+}
+
+func TestTransportH2c(t *testing.T) {
+	l := startH2cServer(t)
+	defer l.Close()
+	req, err := http.NewRequest("GET", "http://"+l.Addr().String()+"/foobar", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tr := &Transport{
+		AllowHTTP: true,
+		DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
+			return net.Dial(network, addr)
+		},
+	}
+	res, err := tr.RoundTrip(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if res.ProtoMajor != 2 {
+		t.Fatal("proto not h2c")
+	}
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := string(body), "Hello, /foobar"; got != want {
+		t.Fatalf("response got %v, want %v", got, want)
+	}
+}
+
 func TestTransport(t *testing.T) {
 func TestTransport(t *testing.T) {
 	const body = "sup"
 	const body = "sup"
 	st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
 	st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {