Files
budget-backend/vendor/github.com/quic-go/quic-go/http3/server.go
kratisto f81c902ca6
Some checks failed
golangci-lint / lint (push) Successful in 1m33s
Test / test (push) Failing after 2m16s
chore: migrate to gitea
2026-01-27 01:56:53 +01:00

746 lines
22 KiB
Go

package http3
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
const NextProtoH3 = "h3"
// StreamType is the stream type of a unidirectional stream.
type StreamType uint64
const (
streamTypeControlStream = 0
streamTypePushStream = 1
streamTypeQPACKEncoderStream = 2
streamTypeQPACKDecoderStream = 3
)
// A QUICListener listens for incoming QUIC connections.
type QUICListener interface {
Accept(context.Context) (*quic.Conn, error)
Addr() net.Addr
io.Closer
}
var _ QUICListener = &quic.EarlyListener{}
// ConfigureTLSConfig creates a new tls.Config which can be used
// to create a quic.Listener meant for serving HTTP/3.
func ConfigureTLSConfig(tlsConf *tls.Config) *tls.Config {
// Workaround for https://github.com/golang/go/issues/60506.
// This initializes the session tickets _before_ cloning the config.
_, _ = tlsConf.DecryptTicket(nil, tls.ConnectionState{})
config := tlsConf.Clone()
config.NextProtos = []string{NextProtoH3}
if gfc := config.GetConfigForClient; gfc != nil {
config.GetConfigForClient = func(ch *tls.ClientHelloInfo) (*tls.Config, error) {
conf, err := gfc(ch)
if conf == nil || err != nil {
return conf, err
}
return ConfigureTLSConfig(conf), nil
}
}
return config
}
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation.
type contextKey struct {
name string
}
func (k *contextKey) String() string { return "quic-go/http3 context value " + k.name }
// ServerContextKey is a context key. It can be used in HTTP
// handlers with Context.Value to access the server that
// started the handler. The associated value will be of
// type *http3.Server.
var ServerContextKey = &contextKey{"http3-server"}
// RemoteAddrContextKey is a context key. It can be used in
// HTTP handlers with Context.Value to access the remote
// address of the connection. The associated value will be of
// type net.Addr.
//
// Use this value instead of [http.Request.RemoteAddr] if you
// require access to the remote address of the connection rather
// than its string representation.
var RemoteAddrContextKey = &contextKey{"remote-addr"}
// listener contains info about specific listener added with addListener
type listener struct {
ln *QUICListener
port int // 0 means that no info about port is available
// if this listener was constructed by the application, it won't be closed when the server is closed
createdLocally bool
}
// Server is a HTTP/3 server.
type Server struct {
// Addr optionally specifies the UDP address for the server to listen on,
// in the form "host:port".
//
// When used by ListenAndServe and ListenAndServeTLS methods, if empty,
// ":https" (port 443) is used. See net.Dial for details of the address
// format.
//
// Otherwise, if Port is not set and underlying QUIC listeners do not
// have valid port numbers, the port part is used in Alt-Svc headers set
// with SetQUICHeaders.
Addr string
// Port is used in Alt-Svc response headers set with SetQUICHeaders. If
// needed Port can be manually set when the Server is created.
//
// This is useful when a Layer 4 firewall is redirecting UDP traffic and
// clients must use a port different from the port the Server is
// listening on.
Port int
// TLSConfig provides a TLS configuration for use by server. It must be
// set for ListenAndServe and Serve methods.
TLSConfig *tls.Config
// QUICConfig provides the parameters for QUIC connection created with Serve.
// If nil, it uses reasonable default values.
//
// Configured versions are also used in Alt-Svc response header set with SetQUICHeaders.
QUICConfig *quic.Config
// Handler is the HTTP request handler to use. If not set, defaults to
// http.NotFound.
Handler http.Handler
// EnableDatagrams enables support for HTTP/3 datagrams (RFC 9297).
// If set to true, QUICConfig.EnableDatagrams will be set.
EnableDatagrams bool
// MaxHeaderBytes controls the maximum number of bytes the server will
// read parsing the request HEADERS frame. It does not limit the size of
// the request body. If zero or negative, http.DefaultMaxHeaderBytes is
// used.
MaxHeaderBytes int
// AdditionalSettings specifies additional HTTP/3 settings.
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
AdditionalSettings map[uint64]uint64
// IdleTimeout specifies how long until idle clients connection should be
// closed. Idle refers only to the HTTP/3 layer, activity at the QUIC layer
// like PING frames are not considered.
// If zero or negative, there is no timeout.
IdleTimeout time.Duration
// ConnContext optionally specifies a function that modifies the context used for a new connection c.
// The provided ctx has a ServerContextKey value.
ConnContext func(ctx context.Context, c *quic.Conn) context.Context
Logger *slog.Logger
mutex sync.RWMutex
listeners []listener
closed bool
closeCtx context.Context // canceled when the server is closed
closeCancel context.CancelFunc // cancels the closeCtx
graceCtx context.Context // canceled when the server is closed or gracefully closed
graceCancel context.CancelFunc // cancels the graceCtx
connCount atomic.Int64
connHandlingDone chan struct{}
altSvcHeader string
}
// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
//
// If s.Addr is blank, ":https" is used.
func (s *Server) ListenAndServe() error {
ln, err := s.setupListenerForConn(s.TLSConfig, nil)
if err != nil {
return err
}
defer s.removeListener(ln)
return s.serveListener(*ln)
}
// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
//
// If s.Addr is blank, ":https" is used.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
var err error
certs := make([]tls.Certificate, 1)
certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
// We currently only use the cert-related stuff from tls.Config,
// so we don't need to make a full copy.
ln, err := s.setupListenerForConn(&tls.Config{Certificates: certs}, nil)
if err != nil {
return err
}
defer s.removeListener(ln)
return s.serveListener(*ln)
}
// Serve an existing UDP connection.
// It is possible to reuse the same connection for outgoing connections.
// Closing the server does not close the connection.
func (s *Server) Serve(conn net.PacketConn) error {
ln, err := s.setupListenerForConn(s.TLSConfig, conn)
if err != nil {
return err
}
defer s.removeListener(ln)
return s.serveListener(*ln)
}
// init initializes the contexts used for shutting down the server.
// It must be called with the mutex held.
func (s *Server) init() {
if s.closeCtx == nil {
s.closeCtx, s.closeCancel = context.WithCancel(context.Background())
s.graceCtx, s.graceCancel = context.WithCancel(s.closeCtx)
}
s.connHandlingDone = make(chan struct{}, 1)
}
func (s *Server) decreaseConnCount() {
if s.connCount.Add(-1) == 0 && s.graceCtx.Err() != nil {
close(s.connHandlingDone)
}
}
// ServeQUICConn serves a single QUIC connection.
func (s *Server) ServeQUICConn(conn *quic.Conn) error {
s.mutex.Lock()
if s.closed {
s.mutex.Unlock()
return http.ErrServerClosed
}
s.init()
s.mutex.Unlock()
s.connCount.Add(1)
defer s.decreaseConnCount()
return s.handleConn(conn)
}
// ServeListener serves an existing QUIC listener.
// Make sure you use http3.ConfigureTLSConfig to configure a tls.Config
// and use it to construct a http3-friendly QUIC listener.
// Closing the server does not close the listener. It is the application's responsibility to close them.
// ServeListener always returns a non-nil error. After Shutdown or Close, the returned error is http.ErrServerClosed.
func (s *Server) ServeListener(ln QUICListener) error {
s.mutex.Lock()
if err := s.addListener(&ln, false); err != nil {
s.mutex.Unlock()
return err
}
s.mutex.Unlock()
defer s.removeListener(&ln)
return s.serveListener(ln)
}
func (s *Server) serveListener(ln QUICListener) error {
for {
conn, err := ln.Accept(s.graceCtx)
// server closed
if errors.Is(err, quic.ErrServerClosed) || s.graceCtx.Err() != nil {
return http.ErrServerClosed
}
if err != nil {
return err
}
s.connCount.Add(1)
go func() {
defer s.decreaseConnCount()
if err := s.handleConn(conn); err != nil {
if s.Logger != nil {
s.Logger.Debug("handling connection failed", "error", err)
}
}
}()
}
}
var errServerWithoutTLSConfig = errors.New("use of http3.Server without TLSConfig")
func (s *Server) setupListenerForConn(tlsConf *tls.Config, conn net.PacketConn) (*QUICListener, error) {
if tlsConf == nil {
return nil, errServerWithoutTLSConfig
}
baseConf := ConfigureTLSConfig(tlsConf)
quicConf := s.QUICConfig
if quicConf == nil {
quicConf = &quic.Config{Allow0RTT: true}
} else {
quicConf = s.QUICConfig.Clone()
}
if s.EnableDatagrams {
quicConf.EnableDatagrams = true
}
s.mutex.Lock()
defer s.mutex.Unlock()
closed := s.closed
if closed {
return nil, http.ErrServerClosed
}
var ln QUICListener
var err error
if conn == nil {
addr := s.Addr
if addr == "" {
addr = ":https"
}
ln, err = quic.ListenAddrEarly(addr, baseConf, quicConf)
} else {
ln, err = quic.ListenEarly(conn, baseConf, quicConf)
}
if err != nil {
return nil, err
}
if err := s.addListener(&ln, true); err != nil {
return nil, err
}
return &ln, nil
}
func extractPort(addr string) (int, error) {
_, portStr, err := net.SplitHostPort(addr)
if err != nil {
return 0, err
}
portInt, err := net.LookupPort("tcp", portStr)
if err != nil {
return 0, err
}
return portInt, nil
}
func (s *Server) generateAltSvcHeader() {
if len(s.listeners) == 0 {
// Don't announce any ports since no one is listening for connections
s.altSvcHeader = ""
return
}
// This code assumes that we will use protocol.SupportedVersions if no quic.Config is passed.
var altSvc []string
addPort := func(port int) {
altSvc = append(altSvc, fmt.Sprintf(`%s=":%d"; ma=2592000`, NextProtoH3, port))
}
if s.Port != 0 {
// if Port is specified, we must use it instead of the
// listener addresses since there's a reason it's specified.
addPort(s.Port)
} else {
// if we have some listeners assigned, try to find ports
// which we can announce, otherwise nothing should be announced
validPortsFound := false
for _, info := range s.listeners {
if info.port != 0 {
addPort(info.port)
validPortsFound = true
}
}
if !validPortsFound {
if port, err := extractPort(s.Addr); err == nil {
addPort(port)
}
}
}
s.altSvcHeader = strings.Join(altSvc, ",")
}
func (s *Server) addListener(l *QUICListener, createdLocally bool) error {
if s.closed {
return http.ErrServerClosed
}
s.init()
laddr := (*l).Addr()
if port, err := extractPort(laddr.String()); err == nil {
s.listeners = append(s.listeners, listener{ln: l, port: port, createdLocally: createdLocally})
} else {
logger := s.Logger
if logger == nil {
logger = slog.Default()
}
logger.Error("Unable to extract port from listener, will not be announced using SetQUICHeaders", "local addr", laddr, "error", err)
s.listeners = append(s.listeners, listener{ln: l, port: 0, createdLocally: createdLocally})
}
s.generateAltSvcHeader()
return nil
}
func (s *Server) removeListener(l *QUICListener) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.listeners = slices.DeleteFunc(s.listeners, func(info listener) bool {
return info.ln == l
})
s.generateAltSvcHeader()
}
func (s *Server) NewRawServerConn(conn *quic.Conn) (*RawServerConn, error) {
hconn, _, _, err := s.newRawServerConn(conn)
if err != nil {
return nil, err
}
return hconn, nil
}
func (s *Server) newRawServerConn(conn *quic.Conn) (*RawServerConn, *quic.SendStream, qlogwriter.Recorder, error) {
var qlogger qlogwriter.Recorder
if qlogTrace := conn.QlogTrace(); qlogTrace != nil && qlogTrace.SupportsSchemas(qlog.EventSchema) {
qlogger = qlogTrace.AddProducer()
}
connCtx := conn.Context()
connCtx = context.WithValue(connCtx, ServerContextKey, s)
connCtx = context.WithValue(connCtx, http.LocalAddrContextKey, conn.LocalAddr())
connCtx = context.WithValue(connCtx, RemoteAddrContextKey, conn.RemoteAddr())
if s.ConnContext != nil {
connCtx = s.ConnContext(connCtx, conn)
if connCtx == nil {
panic("http3: ConnContext returned nil")
}
}
hconn := newRawServerConn(
conn,
s.EnableDatagrams,
s.IdleTimeout,
qlogger,
s.Logger,
connCtx,
s.Handler,
s.maxHeaderBytes(),
)
// open the control stream and send a SETTINGS frame, it's also used to send a GOAWAY frame later
// when the server is gracefully closed
ctrlStr, err := hconn.openControlStream(&settingsFrame{
MaxFieldSectionSize: int64(s.maxHeaderBytes()),
Datagram: s.EnableDatagrams,
ExtendedConnect: true,
Other: s.AdditionalSettings,
})
if err != nil {
return nil, nil, nil, fmt.Errorf("opening the control stream failed: %w", err)
}
return hconn, ctrlStr, qlogger, nil
}
// handleConn handles the HTTP/3 exchange on a QUIC connection.
// It blocks until all HTTP handlers for all streams have returned.
func (s *Server) handleConn(conn *quic.Conn) error {
hconn, ctrlStr, qlogger, err := s.newRawServerConn(conn)
if err != nil {
return err
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
str, err := conn.AcceptUniStream(context.Background())
if err != nil {
return
}
go hconn.HandleUnidirectionalStream(str)
}
}()
var nextStreamID quic.StreamID
var handleErr error
var inGracefulShutdown bool
// Process all requests immediately.
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
ctx := s.graceCtx
for {
// The context used here is:
// * before graceful shutdown: s.graceCtx
// * after graceful shutdown: s.closeCtx
// This allows us to keep accepting (and resetting) streams after graceful shutdown has started.
str, err := conn.AcceptStream(ctx)
if err != nil {
// the underlying connection was closed (by either side)
if conn.Context().Err() != nil {
var appErr *quic.ApplicationError
if !errors.As(err, &appErr) || appErr.ErrorCode != quic.ApplicationErrorCode(ErrCodeNoError) {
handleErr = fmt.Errorf("accepting stream failed: %w", err)
}
break
}
// server (not gracefully) closed, close the connection immediately
if s.closeCtx.Err() != nil {
hconn.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
handleErr = http.ErrServerClosed
break
}
inGracefulShutdown = s.graceCtx.Err() != nil
if !inGracefulShutdown {
var appErr *quic.ApplicationError
if !errors.As(err, &appErr) || appErr.ErrorCode != quic.ApplicationErrorCode(ErrCodeNoError) {
handleErr = fmt.Errorf("accepting stream failed: %w", err)
}
break
}
// gracefully closed, send GOAWAY frame and wait for requests to complete or grace period to end
// new requests will be rejected and shouldn't be sent
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameCreated{
StreamID: ctrlStr.StreamID(),
Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: nextStreamID}},
})
}
wg.Add(1)
// Send the GOAWAY frame in a separate Goroutine.
// Sending might block if the peer didn't grant enough flow control credit.
// Write is guaranteed to return once the connection is closed.
go func() {
defer wg.Done()
_, _ = ctrlStr.Write((&goAwayFrame{StreamID: nextStreamID}).Append(nil))
}()
ctx = s.closeCtx
continue
}
if inGracefulShutdown {
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestRejected))
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestRejected))
continue
}
nextStreamID = str.StreamID() + 4
wg.Add(1)
go func() {
// HandleRequestStream will return once the request has been handled,
// or the underlying connection is closed.
defer wg.Done()
hconn.HandleRequestStream(str)
}()
}
wg.Wait()
return handleErr
}
func (s *Server) maxHeaderBytes() int {
if s.MaxHeaderBytes <= 0 {
return http.DefaultMaxHeaderBytes
}
return s.MaxHeaderBytes
}
// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
// It is the caller's responsibility to close any connection passed to ServeQUICConn.
func (s *Server) Close() error {
s.mutex.Lock()
defer s.mutex.Unlock()
s.closed = true
// server is never used
if s.closeCtx == nil {
return nil
}
s.closeCancel()
var err error
for _, l := range s.listeners {
if l.createdLocally {
if cerr := (*l.ln).Close(); cerr != nil && err == nil {
err = cerr
}
}
}
if s.connCount.Load() == 0 {
return err
}
// wait for all connections to be closed
<-s.connHandlingDone
return err
}
// Shutdown gracefully shuts down the server without interrupting any active connections.
// The server sends a GOAWAY frame first, then or for all running requests to complete.
// Shutdown in combination with ListenAndServe may race if it is called before a UDP socket is established.
// It is recommended to use Serve instead.
func (s *Server) Shutdown(ctx context.Context) error {
s.mutex.Lock()
s.closed = true
// server was never used
if s.closeCtx == nil {
s.mutex.Unlock()
return nil
}
s.graceCancel()
// close all listeners
var closeErrs []error
for _, l := range s.listeners {
if l.createdLocally {
if err := (*l.ln).Close(); err != nil {
closeErrs = append(closeErrs, err)
}
}
}
s.mutex.Unlock()
if len(closeErrs) > 0 {
return errors.Join(closeErrs...)
}
if s.connCount.Load() == 0 {
return s.Close()
}
select {
case <-s.connHandlingDone: // all connections were closed
// When receiving a GOAWAY frame, HTTP/3 clients are expected to close the connection
// once all requests were successfully handled...
return s.Close()
case <-ctx.Done():
// ... however, clients handling long-lived requests (and misbehaving clients),
// might not do so before the context is cancelled.
// In this case, we close the server, which closes all existing connections
// (expect those passed to ServeQUICConn).
_ = s.Close()
return ctx.Err()
}
}
// ErrNoAltSvcPort is the error returned by SetQUICHeaders when no port was found
// for Alt-Svc to announce. This can happen if listening on a PacketConn without a port
// (UNIX socket, for example) and no port is specified in Server.Port or Server.Addr.
var ErrNoAltSvcPort = errors.New("no port can be announced, specify it explicitly using Server.Port or Server.Addr")
// SetQUICHeaders can be used to set the proper headers that announce that this server supports HTTP/3.
// The values set by default advertise all the ports the server is listening on, but can be
// changed to a specific port by setting Server.Port before launching the server.
// If no listener's Addr().String() returns an address with a valid port, Server.Addr will be used
// to extract the port, if specified.
// For example, a server launched using ListenAndServe on an address with port 443 would set:
//
// Alt-Svc: h3=":443"; ma=2592000
func (s *Server) SetQUICHeaders(hdr http.Header) error {
s.mutex.RLock()
defer s.mutex.RUnlock()
if s.altSvcHeader == "" {
return ErrNoAltSvcPort
}
// use the map directly to avoid constant canonicalization since the key is already canonicalized
hdr["Alt-Svc"] = append(hdr["Alt-Svc"], s.altSvcHeader)
return nil
}
// ListenAndServeQUIC listens on the UDP network address addr and calls the
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
// used when handler is nil.
func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
server := &Server{
Addr: addr,
Handler: handler,
}
return server.ListenAndServeTLS(certFile, keyFile)
}
// ListenAndServeTLS listens on the given network address for both TLS/TCP and QUIC
// connections in parallel. It returns if one of the two returns an error.
// http.DefaultServeMux is used when handler is nil.
// The correct Alt-Svc headers for QUIC are set.
func ListenAndServeTLS(addr, certFile, keyFile string, handler http.Handler) error {
// Load certs
var err error
certs := make([]tls.Certificate, 1)
certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
// We currently only use the cert-related stuff from tls.Config,
// so we don't need to make a full copy.
config := &tls.Config{
Certificates: certs,
}
if addr == "" {
addr = ":https"
}
// Open the listeners
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return err
}
udpConn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
defer udpConn.Close()
if handler == nil {
handler = http.DefaultServeMux
}
// Start the servers
quicServer := &Server{
TLSConfig: config,
Handler: handler,
}
hErr := make(chan error, 1)
qErr := make(chan error, 1)
go func() {
hErr <- http.ListenAndServeTLS(addr, certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
quicServer.SetQUICHeaders(w.Header())
handler.ServeHTTP(w, r)
}))
}()
go func() {
qErr <- quicServer.Serve(udpConn)
}()
select {
case err := <-hErr:
quicServer.Close()
return err
case err := <-qErr:
// Cannot close the HTTP server or wait for requests to complete properly :/
return err
}
}