golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
120.91k stars 17.35k forks source link

proposal: net/http: HTTP version selection API #67814

Open neild opened 3 weeks ago

neild commented 3 weeks ago

This issue is part of a project to move x/net/http2 into std: #67810

I propose adding a new mechanism for selecting what HTTP versions will be used by a net/http Server or Transport.

// A Protocol is a bitmask of HTTP protocols.
type Protocol uint64

// Contains reports whether p contains all protocols in v.
func (p Protocol) Contains(v Protocol) bool { return p&v != 0 }

const (
        // HTTP1 is the HTTP/1.0 and HTTP/1.1 protocols.
        // HTTP1 is supported on both unsecured TCP and secured TLS connections.
        HTTP1 Protocol = (1 << iota)

        // HTTP2 is the HTTP/2 protocol over a TLS connection.
        HTTP2
)

type Server { // contains unchanged fields
        // Protocols is the set of protocols accepted by the server.
        // If the set is empty, the default is usually HTTP/1 and HTTP/2.
        // The default is HTTP/1 only if TLSNextProto is non-nil
        // and does not contain an "h2" entry.
        Protocols Protocol
}

type Transport { // contains unchanged fields
        // Protocols is the set of protocols supported by the transport.
        // If zero, the default is usually HTTP/1 only.
        // The default is HTTP/1 and HTTP/2 if ForceAttemptHTTP2 is true,
        // or if TLSNextProto contains an "h2" entry.
        Protocols Protocol
}

Currently, by default

Users may disable HTTP/2 support by setting Server.TLSNextProto or Transport.TLSNextProto to an empty map.

Users may enable HTTP/2 support on a transport by setting Transport.ForceAttemptHTTP2.

Users may disable HTTP/1 support by importing golang.org/x/net/http2 using an http2.Server or http2.Transport directly.

The net/http package does not currently directly support HTTP/3, but if and when it does, there will need to be a mechanism for enabling or disabling HTTP/3.

The existing APIs for selecting a protocol version are confusing, inconsistent, expose internal implementation details, and don't generalize well to additional protocol versions. The above proposal replaces them with a single, clear mechanism that allows for future expansion.

Example usage:

s := &http.Server{}
s.Protocols = HTTP1 // disable HTTP/2 support

tr := &http.Transport{}
tr.Protocols = HTTP1 | HTTP2 // enable HTTP/2 support
neild commented 3 weeks ago

My initial proposal for this in #60746 configured protocols with an ordered []Protocol. After experimenting with implementation, I think that ordered protocol selection is too complicated. (It's not bad with just two protocols, but what happens if we support HTTP/3, someone asks for HTTP/2->HTTP/3->HTTP/1, and ALPN negotiation picks HTTP/1? Do we drop the connection and retry on HTTP/3?)

I'm not sure about the use of a bitmask to represent sets of protocols. It's simple and we're unlikely to ever run out of 64 bits, but perhaps it's a bit too simple? We could have an opaque type instead:

type Protocol int

// Protocols is a set of protocols.
type Protocols struct {}
func (p *Protocols) Add(Protocol)
func (p *Protocols) Remove(Protocol)
func (p Protocols) Contains(Protocol)
func (p Protocols) All() iter.Seq[Protocol]
rsc commented 1 week ago

This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — rsc for the proposal review group

rsc commented 5 days ago

Does it have to be so general at all? An even more pedestrian API would be:

type Protocols struct { ... } func (p Protocols) HTTP1() bool func (p Protocols) HTTP2() bool func (p Protocols) HTTP3() bool func (p Protocols) SetHTTP1(ok bool) func (p Protocols) SetHTTP2(ok bool) func (p Protocols) SetHTTP3(ok bool)

It's not as cute but it doesn't paint us into any corners and it's very clear.

Thoughts?

neild commented 5 days ago

Looks good to me.