Skip to content

net/http: http.Client starts returning errors when tls.Config is shared #73911

Closed as duplicate of#39302
@ioan994

Description

@ioan994

Go version

go version go1.24.2 darwin/amd64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE='on'
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/ipetrenko/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/ipetrenko/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/ml/f6gwv9sn1s91hbj0fzqkswy40000gn/T/go-build2838209554=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/ipetrenko/test/client/go.mod'
GOMODCACHE='/Users/ipetrenko/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/ipetrenko/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/ipetrenko/go/pkg/mod/golang.org/[email protected]'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/ipetrenko/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/ipetrenko/go/pkg/mod/golang.org/[email protected]/pkg/tool/darwin_amd64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Please take a look and run the test snippet below.

import (
	"crypto/tls"
	"net/http"
	"testing"
)

func TestClient(t *testing.T) {
	config := tls.Config{InsecureSkipVerify: true}

	transport1 := &http.Transport{
		TLSClientConfig: &config,
	}
	client1 := &http.Client{Transport: transport1}
	_, err1 := client1.Get("https://www.google.com")
	if err1 != nil {
		t.Error(err1)
	}

	transport2 := &http.Transport{
		ForceAttemptHTTP2: true,
		TLSClientConfig:   &config,
	}
	client2 := &http.Client{Transport: transport2}
	_, err2 := client2.Get("https://www.google.com")
	if err2 != nil {
		t.Error(err2)
	}

	_, err3 := client1.Get("https://www.google.com")
	if err3 != nil {
		t.Error(err3)
	}
}

I created a singe tls.Config object that is shared in two different http.Transport objects. According to the following comment from the crypto/tls/common.go file, config explicitly allows its reuse:

// A Config structure is used to configure a TLS client or server.
// After one has been passed to a TLS function it must not be
// modified. A Config may be reused; the tls package will also not
// modify it.
type Config struct {

One of the transport objects has ForceAttemptHTTP2 field set to true. I use those two http.Transport objects to create two http.Client objects and perform a Get request to www.google.com.

What did you see happen?

First, and second requests succeed. The third request attempt _, err3 := client1.Get("https://www.google.com") errors out and causes the test to fail. It appears that using the client2 to perform a Get request leads to some changes to the tls.Config struct, which breaks the client1 for some reason.

This behaviour is unexpected, because tls.Config documentation promotes sharing of the config object.

What did you expect to see?

I expect all requests to succeed, and test case not fail.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions