package web import ( "context" "embed" "github.com/BurntSushi/toml" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" "html/template" "io" "io/fs" "net" "net/http" "os" "x-ui/config" "x-ui/logger" "x-ui/util/common" "x-ui/web/controller" ) //go:embed assets/* var assetsFS embed.FS //go:embed html/* var htmlFS embed.FS //go:embed translation/* var i18nFS embed.FS type wrapAssetsFS struct { embed.FS } func (f *wrapAssetsFS) Open(name string) (fs.File, error) { return f.FS.Open("assets/" + name) } type Server struct { listener net.Listener index *controller.IndexController server *controller.ServerController xui *controller.XUIController ctx context.Context cancel context.CancelFunc } func NewServer() *Server { return new(Server) } 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() store := cookie.NewStore(config.GetSecret()) engine.Use(sessions.Sessions("session", store)) err := s.initI18n(engine) if err != nil { return nil, err } if config.IsDebug() { // for develop engine.LoadHTMLGlob("web/html/**/*.html") engine.StaticFS(config.GetBasePath()+"assets", http.FS(os.DirFS("web/assets"))) } else { t := template.New("") t, err = t.ParseFS(htmlFS, "html/**/*.html") if err != nil { return nil, err } engine.SetHTMLTemplate(t) engine.StaticFS(config.GetBasePath()+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) } g := engine.Group(config.GetBasePath()) s.index = controller.NewIndexController(g) s.server = controller.NewServerController(g) s.xui = controller.NewXUIController(g) return engine, nil } func (s *Server) initI18n(engine *gin.Engine) error { bundle := i18n.NewBundle(language.SimplifiedChinese) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } data, err := i18nFS.ReadFile(path) if err != nil { return err } _, err = bundle.ParseMessageFileBytes(data, path) return err }) if err != nil { return err } findI18nParamNames := func(key string) []string { names := make([]string, 0) keyLen := len(key) for i := 0; i < keyLen-1; i++ { if key[i:i+2] == "{{" { // 判断开头 "{{" j := i + 2 isFind := false for ; j < keyLen-1; j++ { if key[j:j+2] == "}}" { // 结尾 "}}" isFind = true break } } if isFind { names = append(names, key[i+3:j]) } } } return names } var localizer *i18n.Localizer engine.FuncMap["i18n"] = func(key string, params ...string) (string, error) { names := findI18nParamNames(key) if len(names) != len(params) { return "", common.NewError("find names:", names, "---------- params:", params, "---------- num not equal") } templateData := map[string]interface{}{} for i := range names { templateData[names[i]] = params[i] } return localizer.Localize(&i18n.LocalizeConfig{ MessageID: key, TemplateData: templateData, }) } engine.Use(func(c *gin.Context) { accept := c.GetHeader("Accept-Language") localizer = i18n.NewLocalizer(bundle, accept) c.Set("localizer", localizer) c.Next() }) return nil } func (s *Server) Run() error { engine, err := s.initRouter() if err != nil { return err } certFile := config.GetCertFile() keyFile := config.GetKeyFile() if certFile != "" || keyFile != "" { logger.Info("web server run https on", config.GetListen()) return engine.RunTLS(config.GetListen(), certFile, keyFile) } else { logger.Info("web server run http on", config.GetListen()) return engine.Run(config.GetListen()) } }