| // 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. | |
| //go:build go1.27 && !http2legacy | |
| // Server wrapping a net/http.Server. | |
| package http2 | |
| import ( | |
| "context" | |
| "crypto/tls" | |
| "errors" | |
| "net" | |
| "net/http" | |
| "slices" | |
| "sync" | |
| "time" | |
| ) | |
| type serverInternalState struct { | |
| s1 *http.Server | |
| initOnce sync.Once | |
| serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte) | |
| } | |
| func configureServer(s *http.Server, conf *Server) error { | |
| if s == nil { | |
| panic("nil *http.Server") | |
| } | |
| if conf == nil { | |
| conf = new(Server) | |
| } | |
| if conf.state != nil { | |
| // This isn't a panic in the pre-wrapping implementation, | |
| // but calling ConfigureServer twice with the same http2.Server | |
| // overwrites internal state on the server. | |
| // Make the error explicit and early here. | |
| panic("ConfigureServer may be called only once per Server") | |
| } | |
| if h1, h2 := s, conf; h2.IdleTimeout == 0 { | |
| if h1.IdleTimeout != 0 { | |
| h2.IdleTimeout = h1.IdleTimeout | |
| } else { | |
| h2.IdleTimeout = h1.ReadTimeout | |
| } | |
| } | |
| // Register h2 and http/1.1 ALPN protocols on s.TLSConfig, matching | |
| // the pre-wrapping implementation in server.go, so that TLS listeners | |
| // built from s.TLSConfig still negotiate HTTP/2. | |
| if s.TLSConfig == nil { | |
| s.TLSConfig = new(tls.Config) | |
| } | |
| if !slices.Contains(s.TLSConfig.NextProtos, NextProtoTLS) { | |
| s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, NextProtoTLS) | |
| } | |
| if !slices.Contains(s.TLSConfig.NextProtos, "http/1.1") { | |
| s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "http/1.1") | |
| } | |
| conf.state = &serverInternalState{ | |
| s1: s, | |
| } | |
| sconfig := &serverConfig{s: conf} | |
| if err := s.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil { | |
| panic("http2: net/http does not support this version of x/net/http2") | |
| } | |
| conf.state.serveConnFunc = sconfig.serveConnFunc | |
| return nil | |
| } | |
| type serverConfig struct { | |
| s *Server | |
| serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte) | |
| } | |
| func (*serverConfig) Accept() (net.Conn, error) { | |
| return nil, errors.New("unexpected call to Accept") | |
| } | |
| func (*serverConfig) Close() error { | |
| return nil | |
| } | |
| func (*serverConfig) Addr() net.Addr { | |
| return nil | |
| } | |
| func (s *serverConfig) ServeConnFunc(f func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)) { | |
| s.serveConnFunc = f | |
| } | |
| func (s *serverConfig) HTTP2Config() http.HTTP2Config { | |
| return http.HTTP2Config{ | |
| MaxConcurrentStreams: int(s.s.MaxConcurrentStreams), | |
| MaxDecoderHeaderTableSize: int(s.s.MaxDecoderHeaderTableSize), | |
| MaxEncoderHeaderTableSize: int(s.s.MaxEncoderHeaderTableSize), | |
| MaxReadFrameSize: int(s.s.MaxReadFrameSize), | |
| PermitProhibitedCipherSuites: s.s.PermitProhibitedCipherSuites, | |
| MaxReceiveBufferPerConnection: int(s.s.MaxUploadBufferPerConnection), | |
| MaxReceiveBufferPerStream: int(s.s.MaxUploadBufferPerStream), | |
| SendPingTimeout: s.s.ReadIdleTimeout, | |
| PingTimeout: s.s.PingTimeout, | |
| WriteByteTimeout: s.s.WriteByteTimeout, | |
| CountError: s.s.CountError, | |
| } | |
| } | |
| func (s *serverConfig) IdleTimeout() time.Duration { | |
| return s.s.IdleTimeout | |
| } | |
| type serverConn struct{} | |
| func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, _ func(*serverConn)) { | |
| var serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte) | |
| switch { | |
| case opts.BaseConfig != nil: | |
| // The user has provided us with an http.Server to take configuration from. | |
| // | |
| // We can't send our request to opts.BaseConfig, because an http.Server can | |
| // only be associated with a single http2.Server and the user might | |
| // use this one with several http.Servers. | |
| // | |
| // We can't send our request to s.state.s1, because it doesn't contain | |
| // the right configuration. | |
| // | |
| // So create a one-off copy of opts.BaseConfig and use it. | |
| h1 := &http.Server{ | |
| TLSConfig: opts.BaseConfig.TLSConfig, | |
| ReadTimeout: opts.BaseConfig.ReadTimeout, | |
| ReadHeaderTimeout: opts.BaseConfig.ReadHeaderTimeout, | |
| WriteTimeout: opts.BaseConfig.WriteTimeout, | |
| IdleTimeout: opts.BaseConfig.IdleTimeout, | |
| MaxHeaderBytes: opts.BaseConfig.MaxHeaderBytes, | |
| ConnState: opts.BaseConfig.ConnState, | |
| ErrorLog: opts.BaseConfig.ErrorLog, | |
| BaseContext: opts.BaseConfig.BaseContext, | |
| ConnContext: opts.BaseConfig.ConnContext, | |
| HTTP2: opts.BaseConfig.HTTP2, | |
| } | |
| sconfig := &serverConfig{s: s} | |
| if err := h1.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil { | |
| panic("http2: net/http does not support this version of x/net/http2") | |
| } | |
| serveConnFunc = sconfig.serveConnFunc | |
| case s.state != nil: | |
| serveConnFunc = s.state.serveConnFunc | |
| default: | |
| // Strange-but-true: Server has no concurrency-safe way to initialize | |
| // its internal state, so historically ServeConn just doesn't use any | |
| // persistent state if you don't call ConfigureServer first. | |
| // | |
| // If ConfigureServer hasn't been called, create a one-off http.Server | |
| // for the connection, since we don't have any way to keep one around for reuse. | |
| h1 := &http.Server{} | |
| sconfig := &serverConfig{s: s} | |
| if err := h1.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil { | |
| panic("http2: net/http does not support this version of x/net/http2") | |
| } | |
| serveConnFunc = sconfig.serveConnFunc | |
| } | |
| ctx, cancel := serverConnBaseContext(c, opts) | |
| defer cancel() | |
| serveConnFunc(ctx, c, opts.handler(), opts.SawClientPreface, opts.UpgradeRequest, opts.Settings) | |
| } | |
| // FrameWriteRequest is a request to write a frame. | |
| // | |
| // Deprecated: User-provided write schedulers are deprecated. | |
| type FrameWriteRequest struct { | |
| // Ideally we'd define this in writesched_common.go, | |
| // to avoid duplicating an exported symbol across two files, | |
| // but the changes required to make this work are fairly large. | |
| } | |
| func (wr FrameWriteRequest) StreamID() uint32 { | |
| return 0 | |
| } | |
| func (wr FrameWriteRequest) DataSize() int { | |
| return 0 | |
| } | |
| func (wr FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteRequest, int) { | |
| return FrameWriteRequest{}, FrameWriteRequest{}, 0 | |
| } | |
| func (wr FrameWriteRequest) String() string { | |
| return "" | |
| } | |
| // NewPriorityWriteScheduler is deprecated. | |
| // | |
| // Deprecated: User-provided write schedulers are deprecated. | |
| func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler { | |
| return unsupportedWriteScheduler{} | |
| } | |
| // NewRandomWriteScheduler is deprecated. | |
| // | |
| // Deprecated: User-provided write schedulers are deprecated. | |
| func NewRandomWriteScheduler() WriteScheduler { | |
| return unsupportedWriteScheduler{} | |
| } | |
| type unsupportedWriteScheduler struct{} | |
| func (unsupportedWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {} | |
| func (unsupportedWriteScheduler) CloseStream(streamID uint32) {} | |
| func (unsupportedWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {} | |
| func (unsupportedWriteScheduler) Push(wr FrameWriteRequest) {} | |
| func (unsupportedWriteScheduler) Pop() (wr FrameWriteRequest, ok bool) { | |
| return FrameWriteRequest{}, false | |
| } |