package web import ( "context" "crypto/tls" "embed" "io" "io/fs" "net" "net/http" "s-ui/api" "s-ui/config" "s-ui/logger" "s-ui/middleware" "s-ui/network" "s-ui/service" "strconv" "strings" "github.com/gin-contrib/gzip" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) //go:embed html/* var content embed.FS type Server struct { httpServer *http.Server listener net.Listener ctx context.Context cancel context.CancelFunc settingService service.SettingService } func NewServer() *Server { ctx, cancel := context.WithCancel(context.Background()) return &Server{ ctx: ctx, cancel: cancel, } } func (s *Server) initRouter() (*gin.Engine, error) { if config.IsDebug() { gin.SetMode(gin.DebugMode) } else { gin.DefaultWriter = io.Discard gin.DefaultErrorWriter = io.Discard gin.SetMode(gin.ReleaseMode) } engine := gin.Default() base_url, err := s.settingService.GetWebPath() if err != nil { return nil, err } webDomain, err := s.settingService.GetWebDomain() if err != nil { return nil, err } if webDomain != "" { engine.Use(middleware.DomainValidator(webDomain)) } secret, err := s.settingService.GetSecret() if err != nil { return nil, err } engine.Use(gzip.Gzip(gzip.DefaultCompression)) assetsBasePath := base_url + "assets/" store := cookie.NewStore(secret) engine.Use(sessions.Sessions("session", store)) engine.Use(func(c *gin.Context) { uri := c.Request.RequestURI if strings.HasPrefix(uri, assetsBasePath) { c.Header("Cache-Control", "max-age=31536000") } }) // Serve the assets folder assetsFS, err := fs.Sub(content, "html/assets") if err != nil { panic(err) } engine.StaticFS(assetsBasePath, http.FS(assetsFS)) group_api := engine.Group(base_url + "api") api.NewAPIHandler(group_api) // Load the HTML template engine.LoadHTMLFiles("backend/web/html/index.html") // Serve index.html as the entry point // Handle all other routes by serving index.html engine.NoRoute(func(c *gin.Context) { if !strings.HasPrefix(c.Request.URL.Path, base_url) { c.String(404, "") return } if c.Request.URL.Path != base_url+"login" && !api.IsLogin(c) { c.Redirect(http.StatusTemporaryRedirect, base_url+"login") return } if c.Request.URL.Path == base_url+"login" && api.IsLogin(c) { c.Redirect(http.StatusTemporaryRedirect, base_url) return } c.HTML(http.StatusOK, "index.html", gin.H{"BASE_URL": base_url}) }) return engine, nil } func (s *Server) Start() (err error) { //This is an anonymous function, no function name defer func() { if err != nil { s.Stop() } }() engine, err := s.initRouter() if err != nil { return err } certFile, err := s.settingService.GetCertFile() if err != nil { return err } keyFile, err := s.settingService.GetKeyFile() if err != nil { return err } listen, err := s.settingService.GetListen() if err != nil { return err } port, err := s.settingService.GetPort() if err != nil { return err } listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) listener, err := net.Listen("tcp", listenAddr) if err != nil { return err } if certFile != "" || keyFile != "" { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { listener.Close() return err } c := &tls.Config{ Certificates: []tls.Certificate{cert}, } listener = network.NewAutoHttpsListener(listener) listener = tls.NewListener(listener, c) } if certFile != "" || keyFile != "" { logger.Info("web server run https on", listener.Addr()) } else { logger.Info("web server run http on", listener.Addr()) } s.listener = listener s.httpServer = &http.Server{ Handler: engine, } go func() { s.httpServer.Serve(listener) }() return nil } func (s *Server) Stop() error { s.cancel() var err error if s.httpServer != nil { err = s.httpServer.Shutdown(s.ctx) if err != nil { return err } } if s.listener != nil { err = s.listener.Close() if err != nil { return err } } return nil } func (s *Server) GetCtx() context.Context { return s.ctx }