// Package httpp contains HTTP utilities. package httpp import ( "crypto/tls" "fmt" "log" "net" "net/http" "os" "strings" "time" "golang.org/x/net/http2" "github.com/bluenviron/mediamtx/internal/certloader" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/packetdumper" "github.com/bluenviron/mediamtx/internal/restrictnetwork" ) 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 } } listen := func(network string, address string) (net.Listener, error) { ln, err := net.Listen(network, address) if err != nil { return nil, err } if s.DumpPackets { ln = &packetdumper.Listener{ Wrapped: ln, Prefix: s.DumpPacketsPrefix, } } return ln, nil } tlsListen := func(network string, laddr string, config *tls.Config) (net.Listener, error) { ln, err := listen(network, laddr) if err != nil { return nil, err } if s.DumpPackets { ln = &packetdumper.TLSListener{ Wrapped: ln, TLSConfig: config, } } else { ln = tls.NewListener(ln, config) } return ln, nil } 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() } }