-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: Transport.Clone breaks HTTP/2 #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. |
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: