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.
188 lines
3.7 KiB
Go
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()
|
|
}
|
|
}
|