Skip to content

proposal: net/http: #73297

@turettn

Description

@turettn

Proposal Details

Summary

net/http.DefaultTransport is mutable, and many people clearly do not expect it to be mutated. This proposal suggests the addition of a new function to net/http to return the initial, un-mutated value of DefaultTransport.

Reasoning

In net/http, the DefaultTransport variable is configured to a non-trivial default, but can be mutated or replaced at run-time. This allows people to do all sorts of interesting things. For example:

type myTracer struct{
	base http.RoundTripper
}

func (m *myTracer) RoundTrip(req *http.Request) (*http.Response, error) {
	// Tracing stuff
	resp, err := m.base.RoundTrip(req)
	// Tracing stuff
	return resp, err
}

func init() {
	http.DefaultTransport = &myTracer{base: http.DefaultTransport}
}

Whether or not this is a good idea is debatable, but the pattern is not un-common - a quick search turns up over 1000 people replacing DefaultTransport.

Problem 1: It's really hard to revert changes

Once DefaultTransport is mutated, the only real way to restore it to the defaults is to copy the code from the docs or source, and figure out how to avoid the non-exported function it includes.

Problem 2: People want access to the default transport

Additionally, when people do create their own net/http.Client objects with custom transports, they frequently want to start with the default transport that net/http provides - a common pattern is to cast http.DefaultTransport to *http.Transport, clone it, modify it, and create a client with it. A GitHub code search turns up over 7500 unchecked casts in this fashion. If something has previously replaced or modified http.DefaultTransport, these developers likely will not get what they're expecting. Sometimes they'll get a panic.

This seems to violate the principle of least surprise.

Proposed Change

I would like to add a function that returns a "truly default" *http.Transport to the net/http library.

func GetInitialDefaultTransport() *Transport {
	return &Transport{
		Proxy: ProxyFromEnvironment,
		DialContext: defaultTransportDialContext(&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}),
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}
}

var DefaultTransport RoundTripper = GetInitialDefaultTransport()

This maintains consistently for all existing code, while giving new code a cleaner path forwards that avoids the pitfalls described above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions