blob: caa6dd64ef70e8a5babad5e7e63ae67d9f7be876 [file]
// 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()
}