Closed
Description
What version of Go are you using (go version
)?
$ go1.13 version go version go1.13.1 darwin/amd64 $ go-tip version go version devel +f6c624a22a Wed Oct 16 15:58:33 2019 +0000 darwin/amd64
Does this issue reproduce with the latest release?
Yes, go1.13.1 and tip are affected.
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go1.13 env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/Users/rhys/Library/Caches/go-build" GOENV="/Users/rhys/Library/Application Support/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GONOPROXY="*" GONOSUMDB="*" GOOS="darwin" GOPATH="/Users/rhys/go" GOPRIVATE="*" GOPROXY="direct" GOROOT="/Users/rhys/go/version/go1.13" GOSUMDB="off" GOTMPDIR="" GOTOOLDIR="/Users/rhys/go/version/go1.13/pkg/tool/darwin_amd64" GCCGO="gccgo" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/49/zmds5zsn75z1283vtzxyfr5hj7yjq4/T/go-build721053994=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
I called httptest.NewTLSServer
and used the attached http.Client
to make requests to it.
What did you expect to see?
I expected the requests to the local test server to use HTTP/2. (This is important for me at the moment because I'm writing reproducers for bugs I've seen in HTTP/2 client requests.)
What did you see instead?
The requests are sent over HTTP/1.1.
It looks like this is because the httptest package's TLS setup sets a tls.Config
on the http.Transport
before its first use, which defeats net/http's usual HTTP/2 autoconfiguration.
$ go1.13 test -v ./repro_test.go
=== RUN TestHTTPTestHTTP2
=== RUN TestHTTPTestHTTP2/basic
=== RUN TestHTTPTestHTTP2/advertise-h2
=== RUN TestHTTPTestHTTP2/transport-hacks
2019/10/16 13:23:20 http: TLS handshake error from 127.0.0.1:64474: remote error: tls: bad certificate
--- FAIL: TestHTTPTestHTTP2 (0.14s)
--- FAIL: TestHTTPTestHTTP2/basic (0.00s)
repro_test.go:17: Request is not http/2: "HTTP/1.1"
--- FAIL: TestHTTPTestHTTP2/advertise-h2 (0.00s)
repro_test.go:37: Request is not http/2: "HTTP/1.1"
--- PASS: TestHTTPTestHTTP2/transport-hacks (0.14s)
FAIL
FAIL command-line-arguments 0.193s
FAIL
$ go-tip test -v ./repro_test.go
=== RUN TestHTTPTestHTTP2
=== RUN TestHTTPTestHTTP2/basic
=== RUN TestHTTPTestHTTP2/advertise-h2
=== RUN TestHTTPTestHTTP2/transport-hacks
2019/10/16 13:23:55 http: TLS handshake error from 127.0.0.1:64490: remote error: tls: bad certificate
--- FAIL: TestHTTPTestHTTP2 (0.14s)
--- FAIL: TestHTTPTestHTTP2/basic (0.00s)
repro_test.go:17: Request is not http/2: "HTTP/1.1"
--- FAIL: TestHTTPTestHTTP2/advertise-h2 (0.00s)
repro_test.go:37: Request is not http/2: "HTTP/1.1"
--- PASS: TestHTTPTestHTTP2/transport-hacks (0.14s)
FAIL
FAIL command-line-arguments 1.254s
FAIL
package repro
import (
"crypto/tls"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
)
func TestHTTPTestHTTP2(t *testing.T) {
t.Run("basic", func(t *testing.T) {
var calls int64
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt64(&calls, 1)
if !r.ProtoAtLeast(2, 0) {
t.Errorf("Request is not http/2: %q", r.Proto)
}
}))
defer s.Close()
resp, err := s.Client().Get(s.URL)
if err != nil {
t.Fatalf("HTTP GET: %v", err)
}
resp.Body.Close()
if have, want := atomic.LoadInt64(&calls), int64(1); have != want {
t.Errorf("HTTP handler called %d times, expected %d times", have, want)
}
})
t.Run("advertise-h2", func(t *testing.T) {
var calls int64
s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt64(&calls, 1)
if !r.ProtoAtLeast(2, 0) {
t.Errorf("Request is not http/2: %q", r.Proto)
}
}))
s.TLS = &tls.Config{
NextProtos: []string{"h2"},
}
s.StartTLS()
defer s.Close()
resp, err := s.Client().Get(s.URL)
if err != nil {
t.Fatalf("HTTP GET: %v", err)
}
resp.Body.Close()
if have, want := atomic.LoadInt64(&calls), int64(1); have != want {
t.Errorf("HTTP handler called %d times, expected %d times", have, want)
}
})
t.Run("transport-hacks", func(t *testing.T) {
var calls int64
s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt64(&calls, 1)
if !r.ProtoAtLeast(2, 0) {
t.Errorf("Request is not http/2: %q", r.Proto)
}
}))
s.TLS = &tls.Config{
NextProtos: []string{"h2"},
}
s.StartTLS()
defer s.Close()
transport := s.Client().Transport.(*http.Transport)
clientConfig := transport.TLSClientConfig
transport.TLSClientConfig = nil
// make a request to trigger HTTP/2 autoconfiguration
resp, err := s.Client().Get(s.URL)
if err == nil {
t.Errorf(`Expected harmless "certificate signed by unknown authority" error`)
resp.Body.Close()
}
// now allow the client to connect to the ad-hoc test server
transport.TLSClientConfig.RootCAs = clientConfig.RootCAs
resp, err = s.Client().Get(s.URL)
if err != nil {
t.Fatalf("HTTP GET: %v", err)
}
resp.Body.Close()
if have, want := atomic.LoadInt64(&calls), int64(1); have != want {
t.Errorf("HTTP handler called %d times, expected %d times", have, want)
}
})
}