package api import ( "encoding/json" "github.com/AlexxIT/go2rtc/cmd/app" "github.com/rs/zerolog" "net" "net/http" "os" "strconv" "strings" "sync" ) func Init() { var cfg struct { Mod struct { Listen string `yaml:"listen"` Username string `yaml:"username"` Password string `yaml:"password"` BasePath string `yaml:"base_path"` StaticDir string `yaml:"static_dir"` Origin string `yaml:"origin"` } `yaml:"api"` } // default config cfg.Mod.Listen = ":1984" // load config from YAML app.LoadConfig(&cfg) if cfg.Mod.Listen == "" { return } basePath = cfg.Mod.BasePath log = app.GetLogger("api") initStatic(cfg.Mod.StaticDir) initWS(cfg.Mod.Origin) HandleFunc("api", apiHandler) HandleFunc("api/config", configHandler) HandleFunc("api/exit", exitHandler) HandleFunc("api/ws", apiWS) // ensure we can listen without errors listener, err := net.Listen("tcp", cfg.Mod.Listen) if err != nil { log.Fatal().Err(err).Msg("[api] listen") return } log.Info().Str("addr", cfg.Mod.Listen).Msg("[api] listen") s := http.Server{} s.Handler = http.DefaultServeMux // 4th if cfg.Mod.Origin == "*" { s.Handler = middlewareCORS(s.Handler) // 3rd } if cfg.Mod.Username != "" { s.Handler = middlewareAuth(cfg.Mod.Username, cfg.Mod.Password, s.Handler) // 2nd } if log.Trace().Enabled() { s.Handler = middlewareLog(s.Handler) // 1st } go func() { if err = s.Serve(listener); err != nil { log.Fatal().Err(err).Msg("[api] serve") } }() } // HandleFunc handle pattern with relative path: // - "api/streams" => "{basepath}/api/streams" // - "/streams" => "/streams" func HandleFunc(pattern string, handler http.HandlerFunc) { if len(pattern) == 0 || pattern[0] != '/' { pattern = basePath + "/" + pattern } log.Trace().Str("path", pattern).Msg("[api] register path") http.HandleFunc(pattern, handler) } const StreamNotFound = "stream not found" var basePath string var log zerolog.Logger func middlewareLog(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Trace().Msgf("[api] %s %s %s", r.Method, r.URL, r.RemoteAddr) next.ServeHTTP(w, r) }) } func middlewareAuth(username, password string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.RemoteAddr, "127.") && !strings.HasPrefix(r.RemoteAddr, "[::1]") { user, pass, ok := r.BasicAuth() if !ok || user != username || pass != password { w.Header().Set("Www-Authenticate", `Basic realm="go2rtc"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } } next.ServeHTTP(w, r) }) } func middlewareCORS(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") next.ServeHTTP(w, r) }) } var mu sync.Mutex func apiHandler(w http.ResponseWriter, r *http.Request) { mu.Lock() app.Info["host"] = r.Host mu.Unlock() if err := json.NewEncoder(w).Encode(app.Info); err != nil { log.Warn().Err(err).Caller().Send() } } func exitHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "", http.StatusBadRequest) return } s := r.URL.Query().Get("code") code, _ := strconv.Atoi(s) os.Exit(code) } type Stream struct { Name string `json:"name"` URL string `json:"url"` } func ResponseStreams(w http.ResponseWriter, streams []Stream) { if len(streams) == 0 { http.Error(w, "no streams", http.StatusNotFound) return } var response struct { Streams []Stream `json:"streams"` } response.Streams = streams if err := json.NewEncoder(w).Encode(response); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }