// 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() }