package sub import ( "context" "crypto/tls" "embed" "html/template" "io" "io/fs" "net" "net/http" "os" "strconv" "x-ui/config" "x-ui/logger" "x-ui/util/common" "x-ui/web/middleware" "x-ui/web/network" "x-ui/web/service" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" ) //go:embed html/* var htmlFS embed.FS type Server struct { httpServer *http.Server listener net.Listener sub *SUBController settingService service.SettingService ctx context.Context cancel context.CancelFunc } func NewServer() *Server { ctx, cancel := context.WithCancel(context.Background()) return &Server{ ctx: ctx, cancel: cancel, } } func (s *Server) getHtmlFiles() ([]string, error) { files := make([]string, 0) dir, _ := os.Getwd() err := fs.WalkDir(os.DirFS(dir), "sub/html", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } files = append(files, path) return nil }) if err != nil { return nil, err } return files, nil } func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) { t := template.New("").Funcs(funcMap) err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { newT, err := t.ParseFS(htmlFS, path+"/*.html") if err != nil { // ignore return nil } t = newT } return nil }) if err != nil { return nil, err } return t, nil } 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() basePath, err := s.settingService.GetBasePath() if err != nil { return nil, err } engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/API/"}))) engine.Use(func(c *gin.Context) { c.Set("base_path", basePath) }) // set static files and template if config.IsDebug() { // for development files, err := s.getHtmlFiles() if err != nil { return nil, err } engine.LoadHTMLFiles(files...) } else { // for production template, err := s.getHtmlTemplate(engine.FuncMap) if err != nil { return nil, err } engine.SetHTMLTemplate(template) } subDomain, err := s.settingService.GetSubDomain() if err != nil { return nil, err } if subDomain != "" { engine.Use(middleware.DomainValidatorMiddleware(subDomain)) } LinksPath, err := s.settingService.GetSubPath() if err != nil { return nil, err } JsonPath, err := s.settingService.GetSubJsonPath() if err != nil { return nil, err } Encrypt, err := s.settingService.GetSubEncrypt() if err != nil { return nil, err } ShowInfo, err := s.settingService.GetSubShowInfo() if err != nil { return nil, err } RemarkModel, err := s.settingService.GetRemarkModel() if err != nil { RemarkModel = "-ieo" } SubUpdates, err := s.settingService.GetSubUpdates() if err != nil { SubUpdates = "10" } SubJsonFragment, err := s.settingService.GetSubJsonFragment() if err != nil { SubJsonFragment = "" } SubJsonNoises, err := s.settingService.GetSubJsonNoises() if err != nil { SubJsonNoises = "" } SubJsonMux, err := s.settingService.GetSubJsonMux() if err != nil { SubJsonMux = "" } SubJsonRules, err := s.settingService.GetSubJsonRules() if err != nil { SubJsonRules = "" } g := engine.Group("/") s.sub = NewSUBController( g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules) return engine, nil } func (s *Server) Start() (err error) { // This is an anonymous function, no function name defer func() { if err != nil { s.Stop() } }() subEnable, err := s.settingService.GetSubEnable() if err != nil { return err } if !subEnable { return nil } engine, err := s.initRouter() if err != nil { return err } certFile, err := s.settingService.GetSubCertFile() if err != nil { return err } keyFile, err := s.settingService.GetSubKeyFile() if err != nil { return err } listen, err := s.settingService.GetSubListen() if err != nil { return err } port, err := s.settingService.GetSubPort() 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 { c := &tls.Config{ Certificates: []tls.Certificate{cert}, } listener = network.NewAutoHttpsListener(listener) listener = tls.NewListener(listener, c) logger.Info("Sub server running HTTPS on", listener.Addr()) } else { logger.Error("Error loading certificates:", err) logger.Info("Sub server running HTTP on", listener.Addr()) } } else { logger.Info("Sub server running 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 err1 error var err2 error if s.httpServer != nil { err1 = s.httpServer.Shutdown(s.ctx) } if s.listener != nil { err2 = s.listener.Close() } return common.Combine(err1, err2) } func (s *Server) GetCtx() context.Context { return s.ctx }