| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // This file tests the exported, user-facing parts of Server. | |
| // | |
| // These tests verify that Server behavior is consistent when using either the | |
| // HTTP/2 implementation in this package (x/net/http2), or when using the | |
| // implementation in net/http/internal/http2. | |
| package http2_test | |
| import ( | |
| "context" | |
| "crypto/tls" | |
| "net/http" | |
| "net/http/httptest" | |
| "testing" | |
| "testing/synctest" | |
| "time" | |
| "golang.org/x/net/http2" | |
| ) | |
| // TestAPIServerMaxConcurrentStreams tests the Server.MaxConcurrentStreams field. | |
| func TestAPIServerMaxConcurrentStreams(t *testing.T) { | |
| synctestTest(t, testAPIServerMaxConcurrentStreams) | |
| } | |
| func testAPIServerMaxConcurrentStreams(t testing.TB) { | |
| const maxConcurrentStreams = 10 | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.MaxConcurrentStreams = maxConcurrentStreams | |
| }) | |
| st.wantSettings(map[http2.SettingID]uint32{ | |
| http2.SettingMaxConcurrentStreams: maxConcurrentStreams, | |
| }) | |
| } | |
| // TestAPIServerMaxDecoderHeaderTableSize tests the Server.MaxDecoderHeaderTableSize field. | |
| func TestAPIServerMaxDecoderHeaderTableSize(t *testing.T) { | |
| synctestTest(t, testAPIServerMaxDecoderHeaderTableSize) | |
| } | |
| func testAPIServerMaxDecoderHeaderTableSize(t testing.TB) { | |
| const maxDecoderHeaderTableSize = 10000 | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.MaxDecoderHeaderTableSize = maxDecoderHeaderTableSize | |
| }) | |
| st.wantSettings(map[http2.SettingID]uint32{ | |
| http2.SettingHeaderTableSize: maxDecoderHeaderTableSize, | |
| }) | |
| } | |
| // TestAPIServerMaxEncoderHeaderTableSize should go here, | |
| // but it's difficult to verify the effects of the encoder table. | |
| // TestAPIServerMaxReadFrameSize tests the Server.MaxDecoderReadFrameSize field. | |
| func TestAPIServerMaxReadFrameSize(t *testing.T) { | |
| synctestTest(t, testAPIServerMaxReadFrameSize) | |
| } | |
| func testAPIServerMaxReadFrameSize(t testing.TB) { | |
| const maxReadFrameSize = 20000 | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.MaxReadFrameSize = maxReadFrameSize | |
| }) | |
| st.wantSettings(map[http2.SettingID]uint32{ | |
| http2.SettingMaxFrameSize: maxReadFrameSize, | |
| }) | |
| } | |
| // TestAPIServerPermitProhibitedCipherSuites tests the Server.PermitProhibitedCipherSuites field. | |
| func TestAPIServerPermitProhibitedCipherSuites(t *testing.T) { | |
| synctestTest(t, testAPIServerPermitProhibitedCipherSuites) | |
| } | |
| func testAPIServerPermitProhibitedCipherSuites(t testing.TB) { | |
| prohibitedCipher := func(state *tls.ConnectionState) { | |
| const cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000 | |
| state.CipherSuite = cipher_TLS_NULL_WITH_NULL_NULL | |
| } | |
| // Default: Connection rejected. | |
| st1 := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| prohibitedCipher, | |
| ) | |
| st1.wantGoAway(0, http2.ErrCodeInadequateSecurity) | |
| st1.wantClosed() | |
| // PermitProhibitedCipherSuites set: Connection accepted. | |
| st2 := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| prohibitedCipher, | |
| func(s *http2.Server) { | |
| s.PermitProhibitedCipherSuites = true | |
| }) | |
| st2.greet() | |
| } | |
| // TestAPIServerIdleTimeout tests the | |
| // Server.ReadIdleTimeout and Server.IdleTimeout fields. | |
| func TestAPIServerIdleTimeout(t *testing.T) { | |
| synctestTest(t, testAPIServerIdleTimeout) | |
| } | |
| func testAPIServerIdleTimeout(t testing.TB) { | |
| const idleTimeout = 3 * time.Second | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.IdleTimeout = idleTimeout | |
| }) | |
| st.greet() | |
| st.wantIdle() | |
| // Connection does not close before IdleTimeout. | |
| time.Sleep(idleTimeout - time.Nanosecond) | |
| st.wantIdle() | |
| // Connection is closed after IdleTimeout. | |
| time.Sleep(time.Nanosecond) | |
| st.wantGoAway(0, http2.ErrCodeNo) | |
| } | |
| // TestAPIServerPingTimeout tests the | |
| // Server.ReadIdleTimeout and Server.PingTimeout fields. | |
| func TestAPIServerPingTimeout(t *testing.T) { | |
| synctestTest(t, testAPIServerPingTimeout) | |
| } | |
| func testAPIServerPingTimeout(t testing.TB) { | |
| const readIdleTimeout = 3 * time.Second | |
| const pingTimeout = 5 * time.Second | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.ReadIdleTimeout = readIdleTimeout | |
| s.PingTimeout = pingTimeout | |
| }) | |
| st.greet() | |
| st.wantIdle() | |
| // PING is sent after ReadIdleTimeout. | |
| time.Sleep(readIdleTimeout - time.Nanosecond) | |
| st.wantIdle() | |
| time.Sleep(time.Nanosecond) | |
| st.wantFrameType(http2.FramePing) | |
| // Connection is closed after PingTimeout. | |
| time.Sleep(pingTimeout - time.Nanosecond) | |
| st.wantIdle() | |
| time.Sleep(time.Nanosecond) | |
| st.wantClosed() | |
| } | |
| // TestAPIServerWriteByteTimeout tests the Server.WriteByteTimeout field. | |
| func TestAPIServerWriteByteTimeout(t *testing.T) { | |
| synctestTest(t, testAPIServerWriteByteTimeout) | |
| } | |
| func testAPIServerWriteByteTimeout(t testing.TB) { | |
| const writeByteTimeout = 3 * time.Second | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.WriteByteTimeout = writeByteTimeout | |
| }) | |
| st.greet() | |
| st.wantIdle() | |
| st.cc.(*synctestNetConn).SetReadBufferSize(1) | |
| st.writeHeaders(http2.HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| time.Sleep(writeByteTimeout - time.Nanosecond) | |
| st.wantFrameType(http2.FrameHeaders) | |
| st.writeHeaders(http2.HeadersFrameParam{ | |
| StreamID: 3, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| time.Sleep(2 * writeByteTimeout) | |
| synctest.Wait() | |
| // Drain the partial response frame from the conn, | |
| // after which we can observe that it has been closed. | |
| st.wantClosed() | |
| } | |
| // TestAPIServerMaxUploadBufferPerConnection tests the Server.MaxUploadBufferPerConnection field. | |
| func TestAPIServerMaxUploadBufferPerConnection(t *testing.T) { | |
| synctestTest(t, testAPIServerMaxUploadBufferPerConnection) | |
| } | |
| func testAPIServerMaxUploadBufferPerConnection(t testing.TB) { | |
| const maxUploadBufferPerConnection = http2.InitialWindowSize + 10000 | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.MaxUploadBufferPerConnection = maxUploadBufferPerConnection | |
| }) | |
| st.writePreface() | |
| st.wantFrameType(http2.FrameSettings) | |
| st.wantWindowUpdate(0, maxUploadBufferPerConnection-http2.InitialWindowSize) | |
| } | |
| // TestAPIServerMaxUploadBufferPerStream tests the Server.MaxUploadBufferPerStream field. | |
| func TestAPIServerMaxUploadBufferPerStream(t *testing.T) { | |
| synctestTest(t, testAPIServerMaxUploadBufferPerStream) | |
| } | |
| func testAPIServerMaxUploadBufferPerStream(t testing.TB) { | |
| const maxUploadBufferPerStream = 10000 | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.MaxUploadBufferPerStream = maxUploadBufferPerStream | |
| }) | |
| st.wantSettings(map[http2.SettingID]uint32{ | |
| http2.SettingInitialWindowSize: maxUploadBufferPerStream, | |
| }) | |
| } | |
| // TestAPIServerCountError tests the Server.CountError field. | |
| func TestAPIServerCountError(t *testing.T) { | |
| synctestTest(t, testAPIServerCountError) | |
| } | |
| func testAPIServerCountError(t testing.TB) { | |
| countError := 0 | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, | |
| func(s *http2.Server) { | |
| s.CountError = func(errType string) { | |
| countError++ | |
| } | |
| }) | |
| st.greet() | |
| st.writeHeaders(http2.HeadersFrameParam{ | |
| StreamID: 2, // invalid stream ID | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| synctest.Wait() | |
| if countError != 1 { | |
| t.Errorf("after connection error: CountError called %v times, want 1", countError) | |
| } | |
| } | |
| // TestAPIServeConnOptsContext tests the ServeConnOpts.Context field. | |
| func TestAPIServeConnOptsContext(t *testing.T) { | |
| synctestTest(t, testAPIServeConnOptsContext) | |
| } | |
| func testAPIServeConnOptsContext(t testing.TB) { | |
| st := newServerTester(t, nil, optNoConn) | |
| cli, srv := synctestNetPipe() | |
| t.Cleanup(func() { | |
| cli.Close() | |
| srv.Close() | |
| }) | |
| type testContextKey struct{} | |
| type testContextValue struct{} | |
| baseCtx := context.WithValue(t.Context(), testContextKey{}, testContextValue{}) | |
| go func() { | |
| st.h2server.ServeConn(srv, &http2.ServeConnOpts{ | |
| Context: baseCtx, | |
| Handler: serverTesterHandler{st}, | |
| }) | |
| }() | |
| tc := newTestServerConn(t, cli) | |
| tc.greet() | |
| tc.writeHeaders(http2.HeadersFrameParam{ | |
| StreamID: 1, // invalid stream ID | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| call := st.nextHandlerCall() | |
| callCtx := call.req.Context() | |
| if v := callCtx.Value(testContextKey{}); v != (testContextValue{}) { | |
| t.Errorf("handler context does not inherit from server base context") | |
| } | |
| if got, want := callCtx.Value(http.LocalAddrContextKey), srv.LocalAddr(); got != want { | |
| t.Errorf("handler context LocalAddrContextKey = %v, want %v", got, want) | |
| } | |
| } | |
| // TestAPIServeConnOptsBaseConfig tests the ServeConnOpts.BaseConfig field. | |
| func TestAPIServeConnOptsBaseConfig(t *testing.T) { | |
| synctestTest(t, testAPIServeConnOptsBaseConfig) | |
| } | |
| func testAPIServeConnOptsBaseConfig(t testing.TB) { | |
| st := newServerTester(t, nil, optNoConn) | |
| cli, srv := synctestNetPipe() | |
| t.Cleanup(func() { | |
| cli.Close() | |
| srv.Close() | |
| }) | |
| const maxReadFrameSize = 20000 | |
| go func() { | |
| st.h2server.ServeConn(srv, &http2.ServeConnOpts{ | |
| BaseConfig: &http.Server{ | |
| HTTP2: &http.HTTP2Config{ | |
| MaxReadFrameSize: maxReadFrameSize, | |
| }, | |
| }, | |
| Handler: serverTesterHandler{st}, | |
| }) | |
| }() | |
| tc := newTestServerConn(t, cli) | |
| tc.wantSettings(map[http2.SettingID]uint32{ | |
| http2.SettingMaxFrameSize: maxReadFrameSize, | |
| }) | |
| } | |
| // TestAPIServeConnUpgrade tests using Server.ServeConn to serve an Upgrade: h2c connection. | |
| // | |
| // Upgrade: h2c is deprecated in current RFCs and we regret attempting to support it | |
| // (our support was never good, and never actually tested because Transport doesn't | |
| // support it at all), but it's there, so we support it for now at least. | |
| func TestAPIServeConnUpgrade(t *testing.T) { | |
| synctestTest(t, testAPIServeConnUpgrade) | |
| } | |
| func testAPIServeConnUpgrade(t testing.TB) { | |
| st := newServerTester(t, nil, optNoConn) | |
| cli, srv := synctestNetPipe() | |
| t.Cleanup(func() { | |
| cli.Close() | |
| srv.Close() | |
| }) | |
| req := httptest.NewRequest("GET", "/", nil) | |
| req.Header.Set("Connection", "Upgrade") | |
| req.Header.Set("Upgrade", "h2c") | |
| go func() { | |
| st.h2server.ServeConn(srv, &http2.ServeConnOpts{ | |
| UpgradeRequest: req, | |
| Settings: []byte{}, | |
| Handler: serverTesterHandler{st}, | |
| }) | |
| }() | |
| cli.SetReadDeadline(time.Now()) | |
| cli.autoWait = true | |
| tc := newTestServerConn(t, cli) | |
| tc.greet() | |
| call := st.nextHandlerCall() | |
| call.w.Header().Set("X-Header", "header") | |
| call.w.WriteHeader(404) | |
| call.w.Header().Set(http.TrailerPrefix+"X-Trailer", "trailer") | |
| call.w.Write([]byte("body")) | |
| call.exit() | |
| tc.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| header: http.Header{ | |
| ":status": []string{"404"}, | |
| "x-header": []string{"header"}, | |
| }, | |
| }) | |
| tc.wantData(wantData{ | |
| streamID: 1, | |
| endStream: false, | |
| data: []byte("body"), | |
| }) | |
| tc.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| header: http.Header{ | |
| "x-trailer": []string{"trailer"}, | |
| }, | |
| }) | |
| tc.wantIdle() | |
| tc.writeHeaders(http2.HeadersFrameParam{ | |
| StreamID: 3, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| call = st.nextHandlerCall() | |
| call.exit() | |
| tc.wantHeaders(wantHeader{ | |
| streamID: 3, | |
| endStream: true, | |
| header: http.Header{ | |
| ":status": []string{"200"}, | |
| }, | |
| }) | |
| } | |
| // TestAPIServeConnOptsSawClientPreface tests the ServeConnOpts.SawClientPreface field. | |
| func TestAPIServeConnOptsSawClientPreface(t *testing.T) { | |
| synctestTest(t, testAPIServeConnOptsSawClientPreface) | |
| } | |
| func testAPIServeConnOptsSawClientPreface(t testing.TB) { | |
| st := newServerTester(t, nil, optNoConn) | |
| cli, srv := synctestNetPipe() | |
| t.Cleanup(func() { | |
| cli.Close() | |
| srv.Close() | |
| }) | |
| const maxReadFrameSize = 20000 | |
| go func() { | |
| st.h2server.ServeConn(srv, &http2.ServeConnOpts{ | |
| SawClientPreface: true, | |
| Handler: serverTesterHandler{st}, | |
| }) | |
| }() | |
| tc := newTestServerConn(t, cli) | |
| tc.wantFrameType(http2.FrameSettings) | |
| tc.wantFrameType(http2.FrameWindowUpdate) | |
| tc.writeSettings() | |
| tc.wantFrameType(http2.FrameSettings) // ACK | |
| tc.writeSettingsAck() | |
| } |