repo_description: Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS / MPEG-TS / RTP media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
repo_topics: go golang hls media-server obs-studio rtcp rtmp rtmp-proxy rtmp-server rtp rtsp rtsp-proxy rtsp-relay rtsp-server srt streaming webrtc webrtc-proxy
code: // Package api contains the API server.
  package api //nolint:revive
  
  import (
  	"fmt"
  	"net"
  	"net/http"
  	"reflect"
  	"sort"
  	"sync"
  	"time"
  
  	"github.com/gin-gonic/gin"
  
  	"github.com/bluenviron/mediamtx/internal/auth"
  	"github.com/bluenviron/mediamtx/internal/conf"
  	"github.com/bluenviron/mediamtx/internal/defs"
  	"github.com/bluenviron/mediamtx/internal/logger"
  	"github.com/bluenviron/mediamtx/internal/protocols/httpp"
  )
  
  func interfaceIsEmpty(i any) bool {
  	return reflect.ValueOf(i).Kind() != reflect.Pointer || reflect.ValueOf(i).IsNil()
  }
  
  func sortedKeys(paths map[string]*conf.Path) []string {
  	ret := make([]string, len(paths))
  	i := 0
  	for name := range paths {
  		ret[i] = name
  		i++
  	}
  	sort.Strings(ret)
  	return ret
  }
  
  func paramName(ctx *gin.Context) (string, bool) {
  	name := ctx.Param("name")
  
  	if len(name) < 2 || name[0] != '/' {
  		return "", false
  	}
  
  	return name[1:], true
  }
  
  type apiAuthManager interface {
  	Authenticate(req *auth.Request) (string, *auth.Error)
  	RefreshJWTJWKS()
  }
  
  type apiParent interface {
  	logger.Writer
  	APIConfigSet(conf *conf.Conf)
  }
  
  // API is an API server.
  type API struct {
  	Version        string
  	Started        time.Time
  	Address        string
  	DumpPackets    bool
  	Encryption     bool
  	ServerKey      string
  	ServerCert     string
  	AllowOrigins   []string
  	TrustedProxies conf.IPNetworks
  	ReadTimeout    conf.Duration
  	WriteTimeout   conf.Duration
  	Conf           *conf.Conf
  	AuthManager    apiAuthManager
  	PathManager    defs.APIPathManager
  	RTSPServer     defs.APIRTSPServer
  	RTSPSServer    defs.APIRTSPServer
  	RTMPServer     defs.APIRTMPServer
  	RTMPSServer    defs.APIRTMPServer
  	HLSServer      defs.APIHLSServer
  	WebRTCServer   defs.APIWebRTCServer
  	SRTServer      defs.APISRTServer
  	Parent         apiParent
  
  	httpServer *httpp.Server
  	mutex      sync.RWMutex
  }
  
  // Initialize initializes API.
  func (a *API) Initialize() error {
  	router := gin.New()
  	router.SetTrustedProxies(a.TrustedProxies.ToTrustedProxies()) //nolint:errcheck
  
  	router.Use(a.middlewarePreflightRequests)
  	router.Use(a.middlewareAuth)
  
  	group := router.Group("/v3")
  
  	group.GET("/info", a.onInfo)
  
  	group.POST("/auth/jwks/refresh", a.onAuthJwksRefresh)
  
  	group.GET("/config/global/get", a.onConfigGlobalGet)
  	group.PATCH("/config/global/patch", a.onConfigGlobalPatch)
  
  	group.GET("/config/pathdefaults/get", a.onConfigPathDefaultsGet)
  	group.PATCH("/config/pathdefaults/patch", a.onConfigPathDefaultsPatch)
  
  	group.GET("/config/paths/list", a.onConfigPathsList)
  	group.GET("/config/paths/get/*name", a.onConfigPathsGet)
  	group.POST("/config/paths/add/*name", a.onConfigPathsAdd)
  	group.PATCH("/config/paths/patch/*name", a.onConfigPathsPatch)
  	group.POST("/config/paths/replace/*name", a.onConfigPathsReplace)
  	group.DELETE("/config/paths/delete/*name", a.onConfigPathsDelete)
  
  	group.GET("/paths/list", a.onPathsList)
  	group.GET("/paths/get/*name", a.onPathsGet)
  
  	if !interfaceIsEmpty(a.HLSServer) {
  		group.GET("/hlsmuxers/list", a.onHLSMuxersList)
  		group.GET("/hlsmuxers/get/*name", a.onHLSMuxersGet)
  	}
  
  	if !interfaceIsEmpty(a.RTSPServer) {
  		group.GET("/rtspconns/list", a.onRTSPConnsList)
  		group.GET("/rtspconns/get/:id", a.onRTSPConnsGet)
  		group.GET("/rtspsessions/list", a.onRTSPSessionsList)
  		group.GET("/rtspsessions/get/:id", a.onRTSPSessionsGet)
  		group.POST("/rtspsessions/kick/:id", a.onRTSPSessionsKick)
  	}
  
  	if !interfaceIsEmpty(a.RTSPSServer) {
  		group.GET("/rtspsconns/list", a.onRTSPSConnsList)
  		group.GET("/rtspsconns/get/:id", a.onRTSPSConnsGet)
  		group.GET("/rtspssessions/list", a.onRTSPSSessionsList)
  		group.GET("/rtspssessions/get/:id", a.onRTSPSSessionsGet)
  		group.POST("/rtspssessions/kick/:id", a.onRTSPSSessionsKick)
  	}
  
  	if !interfaceIsEmpty(a.RTMPServer) {
  		group.GET("/rtmpconns/list", a.onRTMPConnsList)
  		group.GET("/rtmpconns/get/:id", a.onRTMPConnsGet)
  		group.POST("/rtmpconns/kick/:id", a.onRTMPConnsKick)
  	}
  
  	if !interfaceIsEmpty(a.RTMPSServer) {
  		group.GET("/rtmpsconns/list", a.onRTMPSConnsList)
  		group.GET("/rtmpsconns/get/:id", a.onRTMPSConnsGet)
  		group.POST("/rtmpsconns/kick/:id", a.onRTMPSConnsKick)
  	}
  
  	if !interfaceIsEmpty(a.WebRTCServer) {
  		group.GET("/webrtcsessions/list", a.onWebRTCSessionsList)
  		group.GET("/webrtcsessions/get/:id", a.onWebRTCSessionsGet)
  		group.POST("/webrtcsessions/kick/:id", a.onWebRTCSessionsKick)
  	}
  
  	if !interfaceIsEmpty(a.SRTServer) {
  		group.GET("/srtconns/list", a.onSRTConnsList)
  		group.GET("/srtconns/get/:id", a.onSRTConnsGet)
  		group.POST("/srtconns/kick/:id", a.onSRTConnsKick)
  	}
  
  	group.GET("/recordings/list", a.onRecordingsList)
  	group.GET("/recordings/get/*name", a.onRecordingsGet)
  	group.DELETE("/recordings/deletesegment", a.onRecordingDeleteSegment)
  
  	a.httpServer = &httpp.Server{
  		Address:           a.Address,
  		AllowOrigins:      a.AllowOrigins,
  		DumpPackets:       a.DumpPackets,
  		DumpPacketsPrefix: "api_server_conn",
  		ReadTimeout:       time.Duration(a.ReadTimeout),
  		WriteTimeout:      time.Duration(a.WriteTimeout),
  		Encryption:        a.Encryption,
  		ServerCert:        a.ServerCert,
  		ServerKey:         a.ServerKey,
  		Handler:           router,
  		Parent:            a,
  	}
  	err := a.httpServer.Initialize()
  	if err != nil {
  		return err
  	}
  
  	str := "listener opened on " + a.Address
  	if !a.Encryption {
  		str += " (TCP/HTTP)"
  	} else {
  		str += " (TCP/HTTPS)"
  	}
  	a.Log(logger.Info, str)
  
  	return nil
  }
  
  // Close closes the API.
  func (a *API) Close() {
  	a.Log(logger.Info, "listener is closing")
  	a.httpServer.Close()
  }
  
  // Log implements logger.Writer.
  func (a *API) Log(level logger.Level, format string, args ...any) {
  	a.Parent.Log(level, "[API] "+format, args...)
  }
  
  func (a *API) writeError(ctx *gin.Context, status int, err error) {
  	// show error in logs
  	a.Log(logger.Error, err.Error())
  
  	// add error to response
  	ctx.AbortWithStatusJSON(status, &defs.APIError{
  		Status: defs.APIErrorStatusError,
  		Error:  err.Error(),
  	})
  }
  
  func (a *API) writeErrorNoLog(ctx *gin.Context, status int, err error) {
  	ctx.AbortWithStatusJSON(status, &defs.APIError{
  		Status: defs.APIErrorStatusError,
  		Error:  err.Error(),
  	})
  }
  
  func (a *API) writeOK(ctx *gin.Context) {
  	ctx.JSON(http.StatusOK, &defs.APIOK{Status: defs.APIOKStatusOK})
  }
  
  func (a *API) middlewarePreflightRequests(ctx *gin.Context) {
  	if ctx.Request.Method == http.MethodOptions &&
  		ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
  		ctx.Header("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PATCH, DELETE")
  		ctx.Header("Access-Control-Allow-Headers", "Authorization, Content-Type")
  		ctx.AbortWithStatus(http.StatusNoContent)
  		return
  	}
  }
  
  func (a *API) middlewareAuth(ctx *gin.Context) {
  	req := &auth.Request{
  		Action:      conf.AuthActionAPI,
  		Query:       ctx.Request.URL.RawQuery,
  		Credentials: httpp.Credentials(ctx.Request),
  		IP:          net.ParseIP(ctx.ClientIP()),
  	}
  
  	_, err := a.AuthManager.Authenticate(req)
  	if err != nil {
  		if err.AskCredentials {
  			ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
  			a.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
  			return
  		}
  
  		a.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), err.Wrapped)
  
  		// wait some seconds to delay brute force attacks
  		<-time.After(auth.PauseAfterError)
  
  		a.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
  		return
  	}
  }
  
  func (a *API) onInfo(ctx *gin.Context) {
  	ctx.JSON(http.StatusOK, &defs.APIInfo{
  		Version: a.Version,
  		Started: a.Started,
  	})
  }
  
  func (a *API) onAuthJwksRefresh(ctx *gin.Context) {
  	a.AuthManager.RefreshJWTJWKS()
  	a.writeOK(ctx)
  }
  
  // ReloadConf is called by core.
  func (a *API) ReloadConf(conf *conf.Conf) {
  	a.mutex.Lock()
  	defer a.mutex.Unlock()
  	a.Conf = conf
  }
  