Files
mediamtx/internal/protocols/httpp/server.go
T
Alessandro RosandGitHub b5b63d02fc support reading and publishing with Media-over-QUIC (#5815)
Media-over-QUIC is a streaming protocol built upon cutting edge
protocols (QUIC, HTTP3) and browser APIs (WebTransport, WebCodecs).
It's slightly faster than WebRTC, has an advanced data recovery
mechanism (placed at the frame level and not at the packet level), it
supports additional codecs (FLAC) and is less complicated to route.
2026-06-02 23:04:24 +02:00

188 lines
3.7 KiB
Go

// Package httpp contains HTTP utilities.
package httpp
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/bluenviron/mediamtx/internal/certloader"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/packetdumper"
"github.com/bluenviron/mediamtx/internal/restrictnetwork"
"golang.org/x/net/http2"
)
type nilWriter struct{}
func (nilWriter) Write(p []byte) (int, error) {
return len(p), nil
}
// Server is a wrapper around http.Server that provides:
// - net.Listener creation and destruction
// - TLS initialization and hot reload
// - exit on panic
// - logging
// - server header
// - filtering of invalid requests
type Server struct {
Address string
AllowOrigins []string
DumpPackets bool
DumpPacketsPrefix string
ReadTimeout time.Duration
WriteTimeout time.Duration
Encryption bool
ServerCert string
ServerKey string
AllowAutoCert bool
Handler http.Handler
Parent logger.Writer
ln net.Listener
inner *http.Server
loader *certloader.CertLoader
tracker *handlerTracker
}
// Initialize initializes a Server.
func (s *Server) Initialize() error {
if s.ReadTimeout == 0 {
return fmt.Errorf("invalid ReadTimeout")
}
if s.WriteTimeout == 0 {
return fmt.Errorf("invalid WriteTimeout")
}
var tlsConfig *tls.Config
if s.Encryption {
if s.ServerCert == "" {
return fmt.Errorf("server cert is missing")
}
s.loader = &certloader.CertLoader{
CertPath: s.ServerCert,
KeyPath: s.ServerKey,
AllowAuto: s.AllowAutoCert,
Parent: s.Parent,
}
err := s.loader.Initialize()
if err != nil {
return err
}
tlsConfig = &tls.Config{
GetCertificate: s.loader.GetCertificate(),
}
}
var network string
var address string
if strings.HasPrefix(s.Address, "unix://") {
network = "unix"
address = s.Address[len("unix://"):]
} else {
network, address = restrictnetwork.Restrict("tcp", s.Address)
}
if network == "unix" {
os.Remove(address)
}
h := s.Handler
h = &handlerOrigin{h, s.AllowOrigins}
h = &handlerServerHeader{h}
h = &handlerFilterRequests{h}
h = &handlerLogger{h, s.Parent}
h = &handlerExitOnPanic{h}
h = &handlerWriteTimeout{h, s.WriteTimeout}
s.tracker = &handlerTracker{h: h}
h = s.tracker
s.inner = &http.Server{
Handler: h,
TLSConfig: tlsConfig,
// applied before reading any request
ReadTimeout: s.ReadTimeout,
// applied after HTTP handler has returned
IdleTimeout: 30 * time.Second,
ErrorLog: log.New(&nilWriter{}, "", 0),
}
if tlsConfig != nil {
err := http2.ConfigureServer(s.inner, &http2.Server{})
if err != nil {
if s.loader != nil {
s.loader.Close()
}
return err
}
}
var listen func(network string, address string) (net.Listener, error)
var tlsListen func(network string, laddr string, config *tls.Config) (net.Listener, error)
if s.DumpPackets {
listen = (&packetdumper.Listen{
Prefix: s.DumpPacketsPrefix,
}).Do
tlsListen = (&packetdumper.TLSListen{
Listen: listen,
}).Do
} else {
listen = net.Listen
tlsListen = tls.Listen
}
if tlsConfig != nil {
var err error
s.ln, err = tlsListen(network, address, tlsConfig)
if err != nil {
if s.loader != nil {
s.loader.Close()
}
return err
}
} else {
var err error
s.ln, err = listen(network, address)
if err != nil {
if s.loader != nil {
s.loader.Close()
}
return err
}
}
if network == "unix" {
os.Chmod(address, 0o755) //nolint:errcheck
}
go s.inner.Serve(s.ln)
return nil
}
// Close closes all resources and waits for all routines to return.
func (s *Server) Close() {
s.ln.Close()
s.inner.Close() //nolint:errcheck
s.tracker.close()
if s.loader != nil {
s.loader.Close()
}
}