net/http/internal/http2: use net/http Transport and Server in tests

Update http2 tests to use a net/http Transport or Server where
possible, rather than the http2 versions of these types.

This changes tests which exercised configuring the http2
package types to now exercise the net/http configuration path.
For example, tests which set http2.Transport.DisableCompression
will now set http.Transport.DisableCompression. We don't care
about the old http2-internal configuration paths, since they
aren't accessible to users.

For #67810

Change-Id: I942c6812321fbd24c94b8aa7215dc60d6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/751302
Reviewed-by: Nicholas Husin <nsh@golang.org>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil
2026-02-27 09:34:19 -08:00
committed by Gopher Robot
parent 93893efc69
commit 4498bf1a90
4 changed files with 430 additions and 522 deletions

View File

@@ -498,6 +498,7 @@ type testTransport struct {
}
func newTestTransport(t testing.TB, opts ...any) *testTransport {
t.Helper()
tt := &testTransport{
t: t,
}
@@ -505,12 +506,19 @@ func newTestTransport(t testing.TB, opts ...any) *testTransport {
tr := &Transport{}
for _, o := range opts {
switch o := o.(type) {
case nil:
case func(*http.Transport):
o(tr.TestTransport())
case func(*Transport):
o(tr)
case *Transport:
tr = o
case func(*http.HTTP2Config):
tr1 := tr.TestTransport()
if tr1.HTTP2 == nil {
tr1.HTTP2 = &http.HTTP2Config{}
}
o(tr1.HTTP2)
default:
t.Fatalf("unknown newTestTransport option type %T", o)
}
}
tt.tr = tr

View File

@@ -65,9 +65,9 @@ func testConfigTransportSettings(t testing.TB) {
func TestConfigPingTimeoutServer(t *testing.T) { synctestTest(t, testConfigPingTimeoutServer) }
func testConfigPingTimeoutServer(t testing.TB) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
}, func(s *Server) {
s.ReadIdleTimeout = 2 * time.Second
s.PingTimeout = 3 * time.Second
}, func(h2 *http.HTTP2Config) {
h2.SendPingTimeout = 2 * time.Second
h2.PingTimeout = 3 * time.Second
})
st.greet()
@@ -79,9 +79,9 @@ func testConfigPingTimeoutServer(t testing.TB) {
func TestConfigPingTimeoutTransport(t *testing.T) { synctestTest(t, testConfigPingTimeoutTransport) }
func testConfigPingTimeoutTransport(t testing.TB) {
tc := newTestClientConn(t, func(tr *Transport) {
tr.ReadIdleTimeout = 2 * time.Second
tr.PingTimeout = 3 * time.Second
tc := newTestClientConn(t, func(h2 *http.HTTP2Config) {
h2.SendPingTimeout = 2 * time.Second
h2.PingTimeout = 3 * time.Second
})
tc.greet()

View File

@@ -10,6 +10,7 @@ import (
"compress/zlib"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"flag"
"fmt"
@@ -31,6 +32,7 @@ import (
"time"
. "net/http/internal/http2"
"net/http/internal/testcert"
"golang.org/x/net/http2/hpack"
)
@@ -115,9 +117,14 @@ func (w twriter) Write(p []byte) (n int, err error) {
}
func newTestServer(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *httptest.Server {
t.Helper()
if handler == nil {
handler = func(w http.ResponseWriter, req *http.Request) {}
}
ts := httptest.NewUnstartedServer(handler)
ts.EnableHTTP2 = true
ts.Config.ErrorLog = log.New(twriter{t: t}, "", log.LstdFlags)
ts.Config.Protocols = protocols("h2")
h2server := new(Server)
for _, opt := range opts {
switch v := opt.(type) {
@@ -125,25 +132,30 @@ func newTestServer(t testing.TB, handler http.HandlerFunc, opts ...interface{})
v(ts)
case func(*http.Server):
v(ts.Config)
case func(*Server):
v(h2server)
case func(*http.HTTP2Config):
if ts.Config.HTTP2 == nil {
ts.Config.HTTP2 = &http.HTTP2Config{}
}
v(ts.Config.HTTP2)
default:
t.Fatalf("unknown newTestServer option type %T", v)
}
}
ConfigureServer(ts.Config, h2server)
// ConfigureServer populates ts.Config.TLSConfig.
// Copy it to ts.TLS as well.
ts.TLS = ts.Config.TLSConfig
if ts.Config.Protocols.HTTP2() {
ts.TLS = testServerTLSConfig
if ts.Config.TLSConfig != nil {
ts.TLS = ts.Config.TLSConfig
}
ts.StartTLS()
} else if ts.Config.Protocols.UnencryptedHTTP2() {
ts.EnableHTTP2 = false // actually just disables HTTP/2 over TLS
ts.Start()
} else {
t.Fatalf("Protocols contains neither HTTP2 nor UnencryptedHTTP2")
}
// Go 1.22 changes the default minimum TLS version to TLS 1.2,
// in order to properly test cases where we want to reject low
// TLS versions, we need to explicitly configure the minimum
// version here.
ts.Config.TLSConfig.MinVersion = tls.VersionTLS10
ts.StartTLS()
t.Cleanup(func() {
ts.CloseClientConnections()
ts.Close()
@@ -172,10 +184,13 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
}
for _, opt := range opts {
switch v := opt.(type) {
case func(*Server):
v(h2server)
case func(*http.Server):
v(h1server)
case func(*http.HTTP2Config):
if h1server.HTTP2 == nil {
h1server.HTTP2 = &http.HTTP2Config{}
}
v(h1server.HTTP2)
case func(*tls.ConnectionState):
v(&tlsState)
default:
@@ -202,6 +217,7 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
if handler == nil {
handler = serverTesterHandler{st}.ServeHTTP
}
h1server.Handler = handler
t.Cleanup(func() {
st.Close()
@@ -1295,9 +1311,9 @@ func testServer_Handler_Sends_WindowUpdate(t testing.TB) {
//
// This also needs to be less than MAX_FRAME_SIZE.
const windowSize = 65535 * 2
st := newServerTester(t, nil, func(s *Server) {
s.MaxUploadBufferPerConnection = windowSize
s.MaxUploadBufferPerStream = windowSize
st := newServerTester(t, nil, func(h2 *http.HTTP2Config) {
h2.MaxReceiveBufferPerConnection = windowSize
h2.MaxReceiveBufferPerStream = windowSize
})
defer st.Close()
@@ -1336,9 +1352,9 @@ func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) {
}
func testServer_Handler_Sends_WindowUpdate_Padding(t testing.TB) {
const windowSize = 65535 * 2
st := newServerTester(t, nil, func(s *Server) {
s.MaxUploadBufferPerConnection = windowSize
s.MaxUploadBufferPerStream = windowSize
st := newServerTester(t, nil, func(h2 *http.HTTP2Config) {
h2.MaxReceiveBufferPerConnection = windowSize
h2.MaxReceiveBufferPerStream = windowSize
})
defer st.Close()
@@ -1748,9 +1764,7 @@ func TestServer_Rejects_PriorityUpdateUnparsable(t *testing.T) {
synctestTest(t, testServer_Rejects_PriorityUnparsable)
}
func testServer_Rejects_PriorityUnparsable(t testing.TB) {
st := newServerTester(t, nil, func(s *Server) {
s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218
})
st := newServerTester(t, nil)
defer st.Close()
st.greet()
st.writePriorityUpdate(1, "Invalid dictionary: ((((")
@@ -2640,7 +2654,10 @@ func testServer_Advertises_Common_Cipher(t testing.TB) {
tlsConfig := tlsConfigInsecure.Clone()
tlsConfig.MaxVersion = tls.VersionTLS12
tlsConfig.CipherSuites = []uint16{requiredSuite}
tr := &Transport{TLSClientConfig: tlsConfig}
tr := &http.Transport{
TLSClientConfig: tlsConfig,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
req, err := http.NewRequest("GET", ts.URL, nil)
@@ -2704,8 +2721,8 @@ func TestServer_MaxDecoderHeaderTableSize(t *testing.T) {
}
func testServer_MaxDecoderHeaderTableSize(t testing.TB) {
wantHeaderTableSize := uint32(InitialHeaderTableSize * 2)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
s.MaxDecoderHeaderTableSize = wantHeaderTableSize
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(h2 *http.HTTP2Config) {
h2.MaxDecoderHeaderTableSize = int(wantHeaderTableSize)
})
defer st.Close()
@@ -2730,8 +2747,8 @@ func TestServer_MaxEncoderHeaderTableSize(t *testing.T) {
}
func testServer_MaxEncoderHeaderTableSize(t testing.TB) {
wantHeaderTableSize := uint32(InitialHeaderTableSize / 2)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
s.MaxEncoderHeaderTableSize = wantHeaderTableSize
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(h2 *http.HTTP2Config) {
h2.MaxEncoderHeaderTableSize = int(wantHeaderTableSize)
})
defer st.Close()
@@ -3058,7 +3075,10 @@ func testServerWritesUndeclaredTrailers(t testing.TB) {
w.Header().Set(http.TrailerPrefix+trailer, value)
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
cl := &http.Client{Transport: tr}
@@ -3715,7 +3735,10 @@ func testExpect100ContinueAfterHandlerWrites(t testing.TB) {
io.WriteString(w, msg2)
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
req, _ := http.NewRequest("POST", ts.URL, io.LimitReader(neverEnding('A'), 2<<20))
@@ -3787,7 +3810,10 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) {
<-unblock
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
// This previously hung on the 4th iteration.
@@ -3856,8 +3882,8 @@ func testServerIdleTimeout(t testing.TB) {
}
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
}, func(h2s *Server) {
h2s.IdleTimeout = 500 * time.Millisecond
}, func(s *http.Server) {
s.IdleTimeout = 500 * time.Millisecond
})
defer st.Close()
@@ -3881,8 +3907,8 @@ func testServerIdleTimeout_AfterRequest(t testing.TB) {
var st *serverTester
st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
time.Sleep(requestTimeout)
}, func(h2s *Server) {
h2s.IdleTimeout = idleTimeout
}, func(s *http.Server) {
s.IdleTimeout = idleTimeout
})
defer st.Close()
@@ -3963,7 +3989,10 @@ func testIssue20704Race(t testing.TB) {
}
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
cl := &http.Client{Transport: tr}
@@ -4254,7 +4283,10 @@ func TestContentEncodingNoSniffing(t *testing.T) {
w.Write(tt.body)
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
req, _ := http.NewRequest("GET", ts.URL, nil)
@@ -4304,9 +4336,9 @@ func testServerWindowUpdateOnBodyClose(t testing.TB) {
}
r.Body.Close()
errc <- nil
}, func(s *Server) {
s.MaxUploadBufferPerConnection = windowSize
s.MaxUploadBufferPerStream = windowSize
}, func(h2 *http.HTTP2Config) {
h2.MaxReceiveBufferPerConnection = windowSize
h2.MaxReceiveBufferPerStream = windowSize
})
defer st.Close()
@@ -4515,8 +4547,8 @@ func TestServerInitialFlowControlWindow(t *testing.T) {
synctestSubtest(t, fmt.Sprint(want), func(t testing.TB) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
}, func(s *Server) {
s.MaxUploadBufferPerConnection = want
}, func(h2 *http.HTTP2Config) {
h2.MaxReceiveBufferPerConnection = int(want)
})
st.writePreface()
st.writeSettings()
@@ -4579,7 +4611,10 @@ func testServerWriteDoesNotRetainBufferAfterReturn(t testing.TB) {
}
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
req, _ := http.NewRequest("GET", ts.URL, nil)
@@ -4618,7 +4653,10 @@ func testServerWriteDoesNotRetainBufferAfterServerClose(t testing.TB) {
}
})
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
tr := &http.Transport{
TLSClientConfig: tlsConfigInsecure,
Protocols: protocols("h2"),
}
defer tr.CloseIdleConnections()
req, _ := http.NewRequest("GET", ts.URL, nil)
@@ -4651,8 +4689,8 @@ func testServerMaxHandlerGoroutines(t testing.TB) {
}
case <-donec:
}
}, func(s *Server) {
s.MaxConcurrentStreams = maxHandlers
}, func(h2 *http.HTTP2Config) {
h2.MaxConcurrentStreams = maxHandlers
})
defer st.Close()
@@ -4906,8 +4944,13 @@ func testServerWriteByteTimeout(t testing.TB) {
const timeout = 1 * time.Second
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
w.Write(make([]byte, 100))
}, func(s *Server) {
s.WriteByteTimeout = timeout
}, func(s *http.Server) {
// Use unencrypted HTTP/2, so a byte written by the server corresponds
// to a byte read by the test. Using TLS adds another layer of buffering
// and timeout management, which aren't really relevant to the test.
s.Protocols = protocols("h2c")
}, func(h2 *http.HTTP2Config) {
h2.WriteByteTimeout = timeout
})
st.greet()
@@ -4936,16 +4979,16 @@ func testServerWriteByteTimeout(t testing.TB) {
func TestServerPingSent(t *testing.T) { synctestTest(t, testServerPingSent) }
func testServerPingSent(t testing.TB) {
const readIdleTimeout = 15 * time.Second
const sendPingTimeout = 15 * time.Second
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
}, func(s *Server) {
s.ReadIdleTimeout = readIdleTimeout
}, func(h2 *http.HTTP2Config) {
h2.SendPingTimeout = sendPingTimeout
})
st.greet()
st.wantIdle()
st.advance(readIdleTimeout)
st.advance(sendPingTimeout)
_ = readFrame[*PingFrame](t, st)
st.wantIdle()
@@ -4957,16 +5000,16 @@ func testServerPingSent(t testing.TB) {
func TestServerPingResponded(t *testing.T) { synctestTest(t, testServerPingResponded) }
func testServerPingResponded(t testing.TB) {
const readIdleTimeout = 15 * time.Second
const sendPingTimeout = 15 * time.Second
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
}, func(s *Server) {
s.ReadIdleTimeout = readIdleTimeout
}, func(h2 *http.HTTP2Config) {
h2.SendPingTimeout = sendPingTimeout
})
st.greet()
st.wantIdle()
st.advance(readIdleTimeout)
st.advance(sendPingTimeout)
pf := readFrame[*PingFrame](t, st)
st.wantIdle()
@@ -5040,46 +5083,20 @@ func TestServerSettingNoRFC7540Priorities(t *testing.T) {
synctestTest(t, testServerSettingNoRFC7540Priorities)
}
func testServerSettingNoRFC7540Priorities(t testing.TB) {
tests := []struct {
ws func() WriteScheduler
wantNoRFC7540Setting bool
}{
{
ws: func() WriteScheduler {
return NewPriorityWriteSchedulerRFC7540(nil)
},
wantNoRFC7540Setting: false,
},
{
ws: NewPriorityWriteSchedulerRFC9218,
wantNoRFC7540Setting: true,
},
{
ws: NewRandomWriteScheduler,
wantNoRFC7540Setting: true,
},
{
ws: NewRoundRobinWriteScheduler,
wantNoRFC7540Setting: true,
},
}
for _, tt := range tests {
st := newServerTester(t, nil, func(s *Server) {
s.NewWriteScheduler = tt.ws
})
defer st.Close()
const wantNoRFC7540Setting = true
st := newServerTester(t, nil)
defer st.Close()
var gotNoRFC7540Setting bool
st.greetAndCheckSettings(func(s Setting) error {
if s.ID != SettingNoRFC7540Priorities {
return nil
}
gotNoRFC7540Setting = s.Val == 1
var gotNoRFC7540Setting bool
st.greetAndCheckSettings(func(s Setting) error {
if s.ID != SettingNoRFC7540Priorities {
return nil
})
if tt.wantNoRFC7540Setting != gotNoRFC7540Setting {
t.Errorf("want SETTINGS_NO_RFC7540_PRIORITIES to be %v, got %v", tt.wantNoRFC7540Setting, gotNoRFC7540Setting)
}
gotNoRFC7540Setting = s.Val == 1
return nil
})
if wantNoRFC7540Setting != gotNoRFC7540Setting {
t.Errorf("want SETTINGS_NO_RFC7540_PRIORITIES to be %v, got %v", wantNoRFC7540Setting, gotNoRFC7540Setting)
}
}
@@ -5098,74 +5115,6 @@ func testServerSettingNoRFC7540PrioritiesInvalid(t testing.TB) {
st.wantGoAway(0, ErrCodeProtocol)
}
// This test documents current behavior, rather than ideal behavior that we
// would necessarily like to see. Refer to go.dev/issues/75936 for details.
func TestServerRFC7540PrioritySmallPayload(t *testing.T) {
synctestTest(t, testServerRFC7540PrioritySmallPayload)
}
func testServerRFC7540PrioritySmallPayload(t testing.TB) {
endTest := false
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
for !endTest {
w.Write([]byte("a"))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}, func(s *Server) {
s.NewWriteScheduler = func() WriteScheduler {
return NewPriorityWriteSchedulerRFC7540(nil)
}
})
if syncConn, ok := st.cc.(*synctestNetConn); ok {
syncConn.SetReadBufferSize(1)
} else {
t.Fatal("Server connection is not synctestNetConn")
}
defer st.Close()
defer func() { endTest = true }()
st.greet()
// Create 5 streams with weight of 0, and another 5 streams with weight of
// 255.
// Since each stream receives an infinite number of bytes, we should expect
// to see that almost all of the response we get are for the streams with
// weight of 255.
for i := 1; i <= 19; i += 2 {
weight := 1
if i > 10 {
weight = 255
}
st.writeHeaders(HeadersFrameParam{
StreamID: uint32(i),
BlockFragment: st.encodeHeader(),
EndStream: true,
EndHeaders: true,
Priority: PriorityParam{StreamDep: 0, Weight: uint8(weight)},
})
synctest.Wait()
}
// In the current implementation however, the response we get are
// distributed equally amongst all the streams, regardless of weight.
streamWriteCount := make(map[uint32]int)
totalWriteCount := 10000
for range totalWriteCount {
f := st.readFrame()
if f == nil {
break
}
streamWriteCount[f.Header().StreamID] += 1
}
for streamID, writeCount := range streamWriteCount {
expectedWriteCount := totalWriteCount / len(streamWriteCount)
errorMargin := expectedWriteCount / 100
if writeCount >= expectedWriteCount+errorMargin || writeCount <= expectedWriteCount-errorMargin {
t.Errorf("Expected stream %v to receive %v±%v writes, got %v", streamID, expectedWriteCount, errorMargin, writeCount)
}
}
}
// This test documents current behavior, rather than ideal behavior that we
// would necessarily like to see. Refer to go.dev/issues/75936 for details.
func TestServerRFC9218PrioritySmallPayload(t *testing.T) {
@@ -5180,8 +5129,6 @@ func testServerRFC9218PrioritySmallPayload(t testing.TB) {
f.Flush()
}
}
}, func(s *Server) {
s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218
})
if syncConn, ok := st.cc.(*synctestNetConn); ok {
syncConn.SetReadBufferSize(1)
@@ -5240,8 +5187,6 @@ func testServerRFC9218Priority(t testing.TB) {
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}, func(s *Server) {
s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218
})
defer st.Close()
if syncConn, ok := st.cc.(*synctestNetConn); ok {
@@ -5295,8 +5240,6 @@ func testServerRFC9218PriorityIgnoredWhenProxied(t testing.TB) {
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}, func(s *Server) {
s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218
})
defer st.Close()
if syncConn, ok := st.cc.(*synctestNetConn); ok {
@@ -5344,8 +5287,6 @@ func testServerRFC9218PriorityAware(t testing.TB) {
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}, func(s *Server) {
s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218
})
defer st.Close()
if syncConn, ok := st.cc.(*synctestNetConn); ok {
@@ -5410,3 +5351,48 @@ func testServerRFC9218PriorityAware(t testing.TB) {
t.Errorf("want stream to be processed one-by-one to completion when aware of priority, got: %v", streamFrameOrder)
}
}
var (
testServerTLSConfig *tls.Config
testClientTLSConfig *tls.Config
)
func init() {
cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
if err != nil {
panic(err)
}
testServerTLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: []string{"h2"},
}
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
panic(err)
}
certpool := x509.NewCertPool()
certpool.AddCert(x509Cert)
testClientTLSConfig = &tls.Config{
InsecureSkipVerify: true,
RootCAs: certpool,
NextProtos: []string{"h2"},
}
}
func protocols(protos ...string) *http.Protocols {
p := new(http.Protocols)
for _, s := range protos {
switch s {
case "h1":
p.SetHTTP1(true)
case "h2":
p.SetHTTP2(true)
case "h2c":
p.SetUnencryptedHTTP2(true)
default:
panic("unknown protocol: " + s)
}
}
return p
}

File diff suppressed because it is too large Load Diff