package main import ( "io/ioutil" "net/http" "os" "path/filepath" "strings" "time" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" "github.com/sirupsen/logrus" ) // Message resp struct type Message struct { Status int `json:"status"` Payload interface{} `json:"payload"` } // HTTPAPIServer start http server routes func HTTPAPIServer() { //Set HTTP API mode log.WithFields(logrus.Fields{ "module": "http_server", "func": "RTSPServer", "call": "Start", }).Infoln("Server HTTP start") var public *gin.Engine if !Storage.ServerHTTPDebug() { gin.SetMode(gin.ReleaseMode) public = gin.New() } else { gin.SetMode(gin.DebugMode) public = gin.Default() } public.Use(CrossOrigin()) //Add private login password protect methods privat := public.Group("/") if Storage.ServerHTTPLogin() != "" && Storage.ServerHTTPPassword() != "" { privat.Use(gin.BasicAuth(gin.Accounts{Storage.ServerHTTPLogin(): Storage.ServerHTTPPassword()})) } /* Static HTML Files Demo Mode */ if Storage.ServerHTTPDemo() { public.LoadHTMLGlob(Storage.ServerHTTPDir() + "/templates/*") public.GET("/", HTTPAPIServerIndex) public.GET("/pages/stream/list", HTTPAPIStreamList) public.GET("/pages/stream/add", HTTPAPIAddStream) public.GET("/pages/stream/edit/:uuid", HTTPAPIEditStream) public.GET("/pages/player/hls/:uuid/:channel", HTTPAPIPlayHls) public.GET("/pages/player/mse/:uuid/:channel", HTTPAPIPlayMse) public.GET("/pages/player/webrtc/:uuid/:channel", HTTPAPIPlayWebrtc) public.GET("/pages/multiview", HTTPAPIMultiview) public.Any("/pages/multiview/full", HTTPAPIFullScreenMultiView) public.GET("/pages/documentation", HTTPAPIServerDocumentation) public.GET("/pages/player/all/:uuid/:channel", HTTPAPIPlayAll) public.GET("/pages/cameras", HTTPAPICameraManagement) public.StaticFS("/static", http.Dir(Storage.ServerHTTPDir()+"/static")) public.GET("/docs/*filepath", HTTPAPIMarkdownRenderer) } /* Stream Control elements */ privat.GET("/streams", HTTPAPIServerStreams) privat.POST("/stream/:uuid/add", HTTPAPIServerStreamAdd) privat.POST("/stream/:uuid/edit", HTTPAPIServerStreamEdit) privat.GET("/stream/:uuid/delete", HTTPAPIServerStreamDelete) privat.GET("/stream/:uuid/reload", HTTPAPIServerStreamReload) privat.GET("/stream/:uuid/info", HTTPAPIServerStreamInfo) /* Streams Multi Control elements */ privat.POST("/streams/multi/control/add", HTTPAPIServerStreamsMultiControlAdd) privat.POST("/streams/multi/control/delete", HTTPAPIServerStreamsMultiControlDelete) /* Camera Management elements */ privat.GET("/cameras", HTTPAPIServerCameras) privat.GET("/cameras/unit/:unitcode", HTTPAPIServerCamerasByUnitCode) privat.POST("/camera/add", HTTPAPIServerCameraAdd) privat.GET("/camera/:uuid", HTTPAPIServerCameraGet) privat.PUT("/camera/:uuid", HTTPAPIServerCameraUpdate) privat.DELETE("/camera/:uuid", HTTPAPIServerCameraDelete) privat.POST("/cameras/refresh", HTTPAPIServerCameraRefresh) privat.GET("/database/status", HTTPAPIServerDatabaseStatus) /* Java Integration API elements */ public.GET("/api/java/cameras", HTTPJavaAPICameras) public.GET("/api/java/streams", HTTPJavaAPIStreams) public.GET("/api/java/camera/:id", HTTPJavaAPICameraDetail) public.GET("/api/java/system/info", HTTPJavaAPISystemInfo) /* Stream Channel elements */ privat.POST("/stream/:uuid/channel/:channel/add", HTTPAPIServerStreamChannelAdd) privat.POST("/stream/:uuid/channel/:channel/edit", HTTPAPIServerStreamChannelEdit) privat.GET("/stream/:uuid/channel/:channel/delete", HTTPAPIServerStreamChannelDelete) privat.GET("/stream/:uuid/channel/:channel/codec", HTTPAPIServerStreamChannelCodec) privat.GET("/stream/:uuid/channel/:channel/reload", HTTPAPIServerStreamChannelReload) privat.GET("/stream/:uuid/channel/:channel/info", HTTPAPIServerStreamChannelInfo) /* Stream video elements */ //HLS public.GET("/stream/:uuid/channel/:channel/hls/live/index.m3u8", HTTPAPIServerStreamHLSM3U8) public.GET("/stream/:uuid/channel/:channel/hls/live/segment/:seq/file.ts", HTTPAPIServerStreamHLSTS) //HLS remote record //public.GET("/stream/:uuid/channel/:channel/hls/rr/:s/:e/index.m3u8", HTTPAPIServerStreamRRM3U8) //public.GET("/stream/:uuid/channel/:channel/hls/rr/:s/:e/:seq/file.ts", HTTPAPIServerStreamRRTS) //HLS LL public.GET("/stream/:uuid/channel/:channel/hlsll/live/index.m3u8", HTTPAPIServerStreamHLSLLM3U8) public.GET("/stream/:uuid/channel/:channel/hlsll/live/init.mp4", HTTPAPIServerStreamHLSLLInit) public.GET("/stream/:uuid/channel/:channel/hlsll/live/segment/:segment/:any", HTTPAPIServerStreamHLSLLM4Segment) public.GET("/stream/:uuid/channel/:channel/hlsll/live/fragment/:segment/:fragment/:any", HTTPAPIServerStreamHLSLLM4Fragment) //MSE public.GET("/stream/:uuid/channel/:channel/mse", HTTPAPIServerStreamMSE) public.POST("/stream/:uuid/channel/:channel/webrtc", HTTPAPIServerStreamWebRTC) //Save fragment to mp4 public.GET("/stream/:uuid/channel/:channel/save/mp4/fragment/:duration", HTTPAPIServerStreamSaveToMP4) /* HTTPS Mode Cert # Key considerations for algorithm "RSA" ≥ 2048-bit openssl genrsa -out server.key 2048 # Key considerations for algorithm "ECDSA" ≥ secp384r1 # List ECDSA the supported curves (openssl ecparam -list_curves) #openssl ecparam -genkey -name secp384r1 -out server.key #Generation of self-signed(x509) public key (PEM-encodings .pem|.crt) based on the private (.key) openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 */ if Storage.ServerHTTPS() { if Storage.ServerHTTPSAutoTLSEnable() { go func() { err := autotls.Run(public, Storage.ServerHTTPSAutoTLSName()+Storage.ServerHTTPSPort()) if err != nil { log.Println("Start HTTPS Server Error", err) } }() } else { go func() { err := public.RunTLS(Storage.ServerHTTPSPort(), Storage.ServerHTTPSCert(), Storage.ServerHTTPSKey()) if err != nil { log.WithFields(logrus.Fields{ "module": "http_router", "func": "HTTPSAPIServer", "call": "ServerHTTPSPort", }).Fatalln(err.Error()) os.Exit(1) } }() } } err := public.Run(Storage.ServerHTTPPort()) if err != nil { log.WithFields(logrus.Fields{ "module": "http_router", "func": "HTTPAPIServer", "call": "ServerHTTPPort", }).Fatalln(err.Error()) os.Exit(1) } } // HTTPAPIServerIndex index file func HTTPAPIServerIndex(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "index", }) } func HTTPAPIServerDocumentation(c *gin.Context) { c.HTML(http.StatusOK, "documentation.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "documentation", }) } func HTTPAPIStreamList(c *gin.Context) { c.HTML(http.StatusOK, "stream_list.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "stream_list", }) } func HTTPAPIPlayHls(c *gin.Context) { c.HTML(http.StatusOK, "play_hls.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "play_hls", "uuid": c.Param("uuid"), "channel": c.Param("channel"), }) } func HTTPAPIPlayMse(c *gin.Context) { c.HTML(http.StatusOK, "play_mse.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "play_mse", "uuid": c.Param("uuid"), "channel": c.Param("channel"), }) } func HTTPAPIPlayWebrtc(c *gin.Context) { c.HTML(http.StatusOK, "play_webrtc.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "play_webrtc", "uuid": c.Param("uuid"), "channel": c.Param("channel"), }) } func HTTPAPIAddStream(c *gin.Context) { c.HTML(http.StatusOK, "add_stream.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "add_stream", }) } func HTTPAPIEditStream(c *gin.Context) { c.HTML(http.StatusOK, "edit_stream.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "edit_stream", "uuid": c.Param("uuid"), }) } func HTTPAPIMultiview(c *gin.Context) { c.HTML(http.StatusOK, "multiview.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "multiview", }) } func HTTPAPIPlayAll(c *gin.Context) { c.HTML(http.StatusOK, "play_all.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "page": "play_all", "uuid": c.Param("uuid"), "channel": c.Param("channel"), }) } type MultiViewOptions struct { Grid int `json:"grid"` Player map[string]MultiViewOptionsGrid `json:"player"` } type MultiViewOptionsGrid struct { UUID string `json:"uuid"` Channel int `json:"channel"` PlayerType string `json:"playerType"` } func HTTPAPIFullScreenMultiView(c *gin.Context) { var createParams MultiViewOptions err := c.ShouldBindJSON(&createParams) if err != nil { log.WithFields(logrus.Fields{ "module": "http_page", "func": "HTTPAPIFullScreenMultiView", "call": "BindJSON", }).Errorln(err.Error()) } log.WithFields(logrus.Fields{ "module": "http_page", "func": "HTTPAPIFullScreenMultiView", "call": "Options", }).Debugln(createParams) c.HTML(http.StatusOK, "fullscreenmulti.tmpl", gin.H{ "port": Storage.ServerHTTPPort(), "streams": Storage.Streams, "version": time.Now().String(), "options": createParams, "page": "fullscreenmulti", "query": c.Request.URL.Query(), }) } // HTTPAPIMarkdownRenderer renders markdown files as HTML func HTTPAPIMarkdownRenderer(c *gin.Context) { filePath := c.Param("filepath") // Remove leading slash if strings.HasPrefix(filePath, "/") { filePath = filePath[1:] } // Construct full file path fullPath := filepath.Join("./docs", filePath) // Check if file exists and has .md extension if !strings.HasSuffix(filePath, ".md") { c.String(http.StatusNotFound, "只支持 .md 文件") return } // Read markdown file markdownBytes, err := ioutil.ReadFile(fullPath) if err != nil { c.String(http.StatusNotFound, "文件未找到") return } // Create markdown parser with extensions extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock p := parser.NewWithExtensions(extensions) doc := p.Parse(markdownBytes) // Create HTML renderer with extensions htmlFlags := html.CommonFlags | html.HrefTargetBlank opts := html.RendererOptions{Flags: htmlFlags} renderer := html.NewRenderer(opts) // Render markdown to HTML htmlContent := markdown.Render(doc, renderer) // Create HTML content by concatenating strings htmlStart := ` API 文档
` htmlEnd := `
` // Combine HTML parts with markdown content finalHTML := htmlStart + string(htmlContent) + htmlEnd c.Header("Content-Type", "text/html; charset=utf-8") c.String(http.StatusOK, finalHTML) } // CrossOrigin Access-Control-Allow-Origin any methods func CrossOrigin() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } }