New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
net/http: disabling HTTP/2 as per documentation does not work #39302
Comments
Thank you for this report @rittneje! It does work as advertised, except that you are using a http.DefaultTransport.Clone() which clones only the exported fields of that Transport and yet http.DefaultTransport is initialized with some internal variables. The problem you are encountering is an inconsistency in what's expected internally and the TLS handling function is returned as nil which causes that error. You can ensure that this works by creating a fresh transport package main
import (
"crypto/tls"
"fmt"
"net/http"
)
func main() {
httpClient := &http.Client{
Transport: &http.Transport{
TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
},
}
resp, err := httpClient.Get("https://google.com/")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
} or alternatively if you want to entirely disable HTTP/2, you can set in your environment GODEBUG=http2client=0 I am going to send a CL showing clear examples for what I've talked about and that'll perhaps serve as a reference for disabling HTTP/2. |
Change https://golang.org/cl/235523 mentions this issue: |
I forgot to mention, that if you also want to use a cloned http.DefaultTransport with HTTP/1, you'll need to ensure: package main
import (
"crypto/tls"
"fmt"
"net/http"
)
func main() {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.ForceAttemptHTTP2 = false
tr.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
tr.TLSClientConfig = &tls.Config{}
httpClient := &http.Client{
Transport: tr,
}
resp, err := httpClient.Get("https://google.com/")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
} which will directly reflect in your programs. |
Hope it helps anyone having this issue with go 1.19. If you first set ForceAttemptHTTP2 to false in the shallow copy, TLSConfig will be nil. func main() {
htShallow := http.DefaultTransport.(*http.Transport)
htShallow.ForceAttemptHTTP2 = false
fmt.Printf("shallow: %+v\n", htShallow.TLSClientConfig)
htClone := http.DefaultTransport.(*http.Transport).Clone()
fmt.Printf("clone: %+v\n", htClone.TLSClientConfig)
}
// shallow: <nil>
// clone: <nil> If you first set ForceAttemptHTTP2 to false in cloned default transport, both TLSConfig will be initialized. func main() {
htClone := http.DefaultTransport.(*http.Transport).Clone()
htClone.ForceAttemptHTTP2 = false
fmt.Printf("clone: %+v\n", htClone.TLSClientConfig)
htShallow := http.DefaultTransport.(*http.Transport)
fmt.Printf("shallow: %+v\n", htShallow.TLSClientConfig)
}
// clone: &{Rand:<nil> Time:<nil> Certificates:[] NameToCertificate:map[] GetCertificate:<nil> GetClientCertificate:<nil> GetConfigForClient:<nil> VerifyPeerCertificate:<nil> VerifyConnection:<nil> RootCAs:<nil> NextProtos:[h2 http/1.1] ServerName: ClientAuth:NoClientCert ClientCAs:<nil> InsecureSkipVerify:false CipherSuites:[] PreferServerCipherSuites:false SessionTicketsDisabled:false SessionTicketKey:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] ClientSessionCache:<nil> MinVersion:0 MaxVersion:0 CurvePreferences:[] DynamicRecordSizingDisabled:false Renegotiation:0 KeyLogWriter:<nil> mutex:{w:{state:0 sema:0} writerSem:0 readerSem:0 readerCount:0 readerWait:0} sessionTicketKeys:[] autoSessionTicketKeys:[]}
// shallow: &{Rand:<nil> Time:<nil> Certificates:[] NameToCertificate:map[] GetCertificate:<nil> GetClientCertificate:<nil> GetConfigForClient:<nil> VerifyPeerCertificate:<nil> VerifyConnection:<nil> RootCAs:<nil> NextProtos:[h2 http/1.1] ServerName: ClientAuth:NoClientCert ClientCAs:<nil> InsecureSkipVerify:false CipherSuites:[] PreferServerCipherSuites:false SessionTicketsDisabled:false SessionTicketKey:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] ClientSessionCache:<nil> MinVersion:0 MaxVersion:0 CurvePreferences:[] DynamicRecordSizingDisabled:false Renegotiation:0 KeyLogWriter:<nil> mutex:{w:{state:0 sema:0} writerSem:0 readerSem:0 readerCount:0 readerWait:0} sessionTicketKeys:[] autoSessionTicketKeys:[]} If you use "ForceAttemptHTTP2 is false and NextProtos contains h2" Transport, the request to the server that ALPN responds with h2 will result in an error. func main() {
htClone := http.DefaultTransport.(*http.Transport).Clone()
htClone.ForceAttemptHTTP2 = false
client := &http.Client{Transport: htClone}
req, _ := http.NewRequest("GET", "https://google.com", nil)
if _, err := client.Do(req); err != nil {
fmt.Println(err)
} else {
fmt.Println("ok")
}
}
// Get "https://google.com": net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x00\x00\x12\x04\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00d\x00\x04\x00\x10\x00\x00\x00\x06\x00\x01\x00\x00\x00\x00\x04\b\x00\x00\x00\x00\x00\x00\x0f\x00\x01\x00\x00\x1e\a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01http2_handshake_failed" My choice is to override NextProtos to http/1.1 only. func main() {
htClone := http.DefaultTransport.(*http.Transport).Clone()
htClone.ForceAttemptHTTP2 = false
htClone.TLSClientConfig.NextProtos = []string{"http/1.1"}
client := &http.Client{Transport: htClone}
req, _ := http.NewRequest("GET", "https://google.com", nil)
if _, err := client.Do(req); err != nil {
fmt.Println(err)
} else {
fmt.Println("ok")
}
}
// ok I made a request to the https server below and confirmed that the Proto is http/1.1. func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%s\n", r.Proto)
})
http.ListenAndServeTLS(":3000", "./cert.pem", "./cert.key", nil)
} It's hard for me to explain why this behavior happens around DefaultTransport, but I find it confusing. |
Can anyone please explain why this is breaking? TLS connections suddenly started breaking between going from go 1.19.2 to 1.19.6 (should there be breaking changed in minor version upgrades?) I am seeing these error messages when building with go 1.19.6
Is this actually a bug in http2 or do we need to do something to support this protocol? |
@scudette That has nothing to do with this issue. Please open a new one. |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I set up an
http.Transport
with a non-nil, empty map forTLSNextProto
in order to disable HTTP/2, as per the documentation.https://golang.org/pkg/net/http/
What did you expect to see?
I expected it to do HTTP/1.1 over TLS.
What did you see instead?
The text was updated successfully, but these errors were encountered: