// 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. package http2_test import ( "bytes" "crypto/tls" "flag" "io" "log" "net" "net/http" "os" "strings" "sync" "testing" "testing/synctest" "time" . "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" ) var stderrVerbose = flag.Bool("stderr_verbose", false, "Mirror verbosity to stderr, unbuffered") func stderrv() io.Writer { if *stderrVerbose { return os.Stderr } return io.Discard } type safeBuffer struct { b bytes.Buffer m sync.Mutex } func (sb *safeBuffer) Write(d []byte) (int, error) { sb.m.Lock() defer sb.m.Unlock() return sb.b.Write(d) } func (sb *safeBuffer) Bytes() []byte { sb.m.Lock() defer sb.m.Unlock() return sb.b.Bytes() } func (sb *safeBuffer) Len() int { sb.m.Lock() defer sb.m.Unlock() return sb.b.Len() } type serverTester struct { cc net.Conn // client conn t testing.TB h1server *http.Server h2server *Server serverLogBuf safeBuffer // logger for httptest.Server logFilter []string // substrings to filter out scMu sync.Mutex // guards sc sc *ServerConn testConnFramer callsMu sync.Mutex calls []*serverHandlerCall // If http2debug!=2, then we capture Frame debug logs that will be written // to t.Log after a test fails. The read and write logs use separate locks // and buffers so we don't accidentally introduce synchronization between // the read and write goroutines, which may hide data races. frameReadLogMu sync.Mutex frameReadLogBuf bytes.Buffer frameWriteLogMu sync.Mutex frameWriteLogBuf bytes.Buffer // writing headers: headerBuf bytes.Buffer hpackEnc *hpack.Encoder } type twriter struct { t testing.TB st *serverTester // optional } func (w twriter) Write(p []byte) (n int, err error) { if w.st != nil { ps := string(p) for _, phrase := range w.st.logFilter { if strings.Contains(ps, phrase) { return len(p), nil // no logging } } } w.t.Logf("%s", p) return len(p), nil } type serverTesterOpt string var ( optFramerReuseFrames = serverTesterOpt("frame_reuse_frames") // optNoConn indicates that newServerTester should not create a connection. // The test will create its own. optNoConn = serverTesterOpt("no_conn") optQuiet = func(server *http.Server) { server.ErrorLog = log.New(io.Discard, "", 0) } ) func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *serverTester { t.Helper() h1server := &http.Server{} h2server := &Server{} tlsState := tls.ConnectionState{ Version: tls.VersionTLS13, ServerName: "go.dev", CipherSuite: tls.TLS_AES_128_GCM_SHA256, NegotiatedProtocol: "h2", } noConn := false for _, opt := range opts { switch v := opt.(type) { case func(*Server): v(h2server) case func(*http.Server): v(h1server) case func(*tls.ConnectionState): v(&tlsState) case serverTesterOpt: switch v { case optNoConn: noConn = true default: t.Fatalf("unhandled option %v", v) } default: t.Fatalf("unknown newServerTester option type %T", v) } } ConfigureServer(h1server, h2server) cli, srv := synctestNetPipe() cli.SetReadDeadline(time.Now()) cli.autoWait = true t.Cleanup(func() { cli.Close() srv.Close() }) st := &serverTester{ t: t, cc: cli, h1server: h1server, h2server: h2server, } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) if h1server.ErrorLog == nil { h1server.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, &st.serverLogBuf), "", log.LstdFlags) } if handler == nil { handler = serverTesterHandler{st}.ServeHTTP } t.Cleanup(func() { st.Close() time.Sleep(GoAwayTimeout) // give server time to shut down }) if noConn { return st } var connc chan *ServerConn if !wrappedAPI { connc = make(chan *ServerConn) h2server.TestSetNewConnFunc(func(sc *ServerConn) { connc <- sc close(connc) // panic if we unexpectedly get a second conn }) } go func() { h2server.ServeConn(&netConnWithConnectionState{ Conn: srv, state: tlsState, }, &ServeConnOpts{ Handler: handler, BaseConfig: h1server, }) }() if !wrappedAPI { st.sc = <-connc } st.testConnFramer = testConnFramer{ t: t, fr: NewFramer(st.cc, st.cc), dec: hpack.NewDecoder(InitialHeaderTableSize, nil), } synctest.Wait() return st } // testServerConn is a single connection to a server. type testServerConn struct { t testing.TB netconn *synctestNetConn testConnFramer } func newTestServerConn(t testing.TB, nc *synctestNetConn) *testServerConn { tc := &testServerConn{ t: t, netconn: nc, } tc.testConnFramer = testConnFramer{ t: t, fr: NewFramer(nc, nc), dec: hpack.NewDecoder(InitialHeaderTableSize, nil), } return tc } func (tc *testServerConn) writePreface() { tc.t.Helper() n, err := tc.netconn.Write([]byte(ClientPreface)) if err != nil { tc.t.Fatalf("Error writing client preface: %v", err) } if n != len(ClientPreface) { tc.t.Fatalf("Writing client preface, wrote %d bytes; want %d", n, len(ClientPreface)) } } func (tc *testServerConn) greet() { tc.t.Helper() tc.writePreface() tc.wantFrameType(FrameSettings) tc.wantFrameType(FrameWindowUpdate) tc.writeSettings() tc.wantFrameType(FrameSettings) // ACK tc.writeSettingsAck() } type netConnWithConnectionState struct { net.Conn state tls.ConnectionState } func (c *netConnWithConnectionState) ConnectionState() tls.ConnectionState { return c.state } type serverTesterHandler struct { st *serverTester } func (h serverTesterHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { call := &serverHandlerCall{ w: w, req: req, ch: make(chan func()), } h.st.t.Cleanup(call.exit) h.st.callsMu.Lock() h.st.calls = append(h.st.calls, call) h.st.callsMu.Unlock() for f := range call.ch { f() } } // serverHandlerCall is a call to the server handler's ServeHTTP method. type serverHandlerCall struct { w http.ResponseWriter req *http.Request closeOnce sync.Once ch chan func() } // do executes f in the handler's goroutine. func (call *serverHandlerCall) do(f func(http.ResponseWriter, *http.Request)) { donec := make(chan struct{}) call.ch <- func() { defer close(donec) f(call.w, call.req) } <-donec } // exit causes the handler to return. func (call *serverHandlerCall) exit() { call.closeOnce.Do(func() { close(call.ch) }) } func (st *serverTester) Close() { if st.t.Failed() { st.frameReadLogMu.Lock() if st.frameReadLogBuf.Len() > 0 { st.t.Logf("Framer read log:\n%s", st.frameReadLogBuf.String()) } st.frameReadLogMu.Unlock() st.frameWriteLogMu.Lock() if st.frameWriteLogBuf.Len() > 0 { st.t.Logf("Framer write log:\n%s", st.frameWriteLogBuf.String()) } st.frameWriteLogMu.Unlock() // If we failed already (and are likely in a Fatal, // unwindowing), force close the connection, so the // httptest.Server doesn't wait forever for the conn // to close. if st.cc != nil { st.cc.Close() } } if st.cc != nil { st.cc.Close() } log.SetOutput(os.Stderr) } // greet initiates the client's HTTP/2 connection into a state where // frames may be sent. func (st *serverTester) greet() { st.t.Helper() st.greetAndCheckSettings(func(Setting) error { return nil }) } func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error) { st.t.Helper() st.writePreface() st.writeSettings() st.sync() readFrame[*SettingsFrame](st.t, st).ForeachSetting(checkSetting) st.writeSettingsAck() // The initial WINDOW_UPDATE and SETTINGS ACK can come in any order. var gotSettingsAck bool var gotWindowUpdate bool for i := 0; i < 2; i++ { f := st.readFrame() if f == nil { st.t.Fatal("wanted a settings ACK and window update, got none") } switch f := f.(type) { case *SettingsFrame: if !f.Header().Flags.Has(FlagSettingsAck) { st.t.Fatal("Settings Frame didn't have ACK set") } gotSettingsAck = true case *WindowUpdateFrame: if f.FrameHeader.StreamID != 0 { st.t.Fatalf("WindowUpdate StreamID = %d; want 0", f.FrameHeader.StreamID) } gotWindowUpdate = true default: st.t.Fatalf("Wanting a settings ACK or window update, received a %T", f) } } if !gotSettingsAck { st.t.Fatalf("Didn't get a settings ACK") } if !gotWindowUpdate { st.t.Fatalf("Didn't get a window update") } } func (st *serverTester) writePreface() { n, err := st.cc.Write([]byte(ClientPreface)) if err != nil { st.t.Fatalf("Error writing client preface: %v", err) } if n != len(ClientPreface) { st.t.Fatalf("Writing client preface, wrote %d bytes; want %d", n, len(ClientPreface)) } } func (st *serverTester) encodeHeaderField(k, v string) { err := st.hpackEnc.WriteField(hpack.HeaderField{Name: k, Value: v}) if err != nil { st.t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err) } } // encodeHeaderRaw is the magic-free version of encodeHeader. // It takes 0 or more (k, v) pairs and encodes them. func (st *serverTester) encodeHeaderRaw(headers ...string) []byte { if len(headers)%2 == 1 { panic("odd number of kv args") } st.headerBuf.Reset() for len(headers) > 0 { k, v := headers[0], headers[1] st.encodeHeaderField(k, v) headers = headers[2:] } return st.headerBuf.Bytes() } // encodeHeader encodes headers and returns their HPACK bytes. headers // must contain an even number of key/value pairs. There may be // multiple pairs for keys (e.g. "cookie"). The :method, :path, and // :scheme headers default to GET, / and https. The :authority header // defaults to st.ts.Listener.Addr(). func (st *serverTester) encodeHeader(headers ...string) []byte { if len(headers)%2 == 1 { panic("odd number of kv args") } st.headerBuf.Reset() defaultAuthority := st.authority() if len(headers) == 0 { // Fast path, mostly for benchmarks, so test code doesn't pollute // profiles when we're looking to improve server allocations. st.encodeHeaderField(":method", "GET") st.encodeHeaderField(":scheme", "https") st.encodeHeaderField(":authority", defaultAuthority) st.encodeHeaderField(":path", "/") return st.headerBuf.Bytes() } if len(headers) == 2 && headers[0] == ":method" { // Another fast path for benchmarks. st.encodeHeaderField(":method", headers[1]) st.encodeHeaderField(":scheme", "https") st.encodeHeaderField(":authority", defaultAuthority) st.encodeHeaderField(":path", "/") return st.headerBuf.Bytes() } pseudoCount := map[string]int{} keys := []string{":method", ":scheme", ":authority", ":path"} vals := map[string][]string{ ":method": {"GET"}, ":scheme": {"https"}, ":authority": {defaultAuthority}, ":path": {"/"}, } for len(headers) > 0 { k, v := headers[0], headers[1] headers = headers[2:] if _, ok := vals[k]; !ok { keys = append(keys, k) } if strings.HasPrefix(k, ":") { pseudoCount[k]++ if pseudoCount[k] == 1 { vals[k] = []string{v} } else { // Allows testing of invalid headers w/ dup pseudo fields. vals[k] = append(vals[k], v) } } else { vals[k] = append(vals[k], v) } } for _, k := range keys { for _, v := range vals[k] { st.encodeHeaderField(k, v) } } return st.headerBuf.Bytes() } // bodylessReq1 writes a HEADERS frames with StreamID 1 and EndStream and EndHeaders set. func (st *serverTester) bodylessReq1(headers ...string) { st.writeHeaders(HeadersFrameParam{ StreamID: 1, // clients send odd numbers BlockFragment: st.encodeHeader(headers...), EndStream: true, EndHeaders: true, }) } // sync waits for all goroutines to idle. func (st *serverTester) sync() { synctest.Wait() } // advance advances synthetic time by a duration. func (st *serverTester) advance(d time.Duration) { time.Sleep(d) synctest.Wait() } func (st *serverTester) authority() string { return "dummy.tld" } func (st *serverTester) addLogFilter(phrase string) { st.logFilter = append(st.logFilter, phrase) } func (st *serverTester) nextHandlerCall() *serverHandlerCall { st.t.Helper() synctest.Wait() st.callsMu.Lock() defer st.callsMu.Unlock() if len(st.calls) == 0 { st.t.Fatal("expected server handler call, got none") } call := st.calls[0] st.calls = st.calls[1:] return call }