Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
What version of Go are you using (
@odeke-em thanks, but I don't think it does. I can avoid this error by explicitly configuring the protocols (either including http2 or not). I was more reporting the fact that this configuration causes the client to negotiate http/2 but then request using http1.
The behavior is definitely inconsistent.
// TLSClientConfig specifies the TLS configuration to use with // tls.Client. // If nil, the default configuration is used. // If non-nil, HTTP/2 support may not be enabled by default. TLSClientConfig *tls.Config
Note "may not be enabled". As you point out, the code does not enable HTTP/2 when TLCClientConfig is specified.
I'm guessing the H2 server was written that way to allow callers to specify the cert, while it's less common for H2 clients to provide a cert. I think the Transport should behave like the server, to allow clients to provide certs where needed.
To add a little bit of context here: this is caused by the same
When the server starts serving, it correctly detects that it can indeed serve HTTP2. Because it has detected that it can serve HTTP2, it appends
Note that it first clones the
Therefore, when the server decides that it can serve
The two protocol levels in the client disagree here -- TLS has been negotiated with HTTP2 being the next protocol, but the
There's also a data race, since the goroutine running ListenAndServeTLS is also initializing the * tls.Config.NextProtos concurrently with the main goroutine's http Client reading it.
You can observe that by adding the lines:
time.Sleep(500 * time.Millisecond) log.Printf("TLS config NextProto = %q", config.NextProtos)
... to the main.go code above, adding it after the goroutine, before the client initialization. Comment-out the 500ms sleep and you'll need NextProtos sometimes be empty. Running with -race shows:
To fix this all, I think we'd need to stop mutating the caller's provided *Server, at least via its public fields. We might need to add an addition private field (likely guarded by the existing private mutex) and have the http2.ConfigureServer set that instead, to a clone of the exported config, if any. But since that's in a different package, we'd need an exported setter method? (Gross.) Or we'd need to do some trickery that only works in the bundled x/net/http2-in-std version, which is probably what the answer will be.
Then whenever the net/http Server needs its config, it would get it via a private accessor method that prefers the unexported TLS config field over the exported one.
Unrelated, we could also be smarter in the HTTP/1 Transport to check its TLS connection's state and if it had negotiated ALPN "h2", either fail early with something useful ("you configured it wrong.") or go out of its way to actually speak HTTP/2, if that isn't too much work.
In any case, there's too much to do here for Go 1.10, which is due out soon. Sorry.
At least there are plenty of workarounds.