123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- 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"
- "runtime"
- "strconv"
- "time"
- "x-ui/config"
- "x-ui/logger"
- "x-ui/util/common"
- "x-ui/web/controller"
- "x-ui/web/service"
- )
- //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)
- }
- func stopServer(s *Server) {
- s.Stop()
- }
- type Server struct {
- *server
- }
- func NewServer() *Server {
- s := &Server{newServer()}
- runtime.SetFinalizer(s, stopServer)
- return s
- }
- type server struct {
- listener net.Listener
- index *controller.IndexController
- server *controller.ServerController
- xui *controller.XUIController
- xrayService service.XrayService
- 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) 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()
- secret, err := s.settingService.GetSecret()
- if err != nil {
- return nil, err
- }
- basePath, err := s.settingService.GetBasePath()
- if err != nil {
- return nil, err
- }
- store := cookie.NewStore(secret)
- engine.Use(sessions.Sessions("session", store))
- engine.Use(func(c *gin.Context) {
- c.Set("base_path", basePath)
- })
- err = s.initI18n(engine)
- if err != nil {
- return nil, err
- }
- if config.IsDebug() {
- // for develop
- engine.LoadHTMLGlob("web/html/**/*.html")
- engine.StaticFS(basePath+"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(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS}))
- }
- g := engine.Group(basePath)
- 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) startTask() {
- go func() {
- err := s.xrayService.StartXray()
- if err != nil {
- logger.Warning("start xray failed:", err)
- }
- ticker := time.NewTicker(time.Second * 30)
- defer ticker.Stop()
- for {
- select {
- case <-s.ctx.Done():
- return
- case <-ticker.C:
- }
- if s.xrayService.IsXrayRunning() {
- continue
- }
- err := s.xrayService.StartXray()
- if err != nil {
- logger.Warning("start xray failed:", err)
- }
- }
- }()
- }
- func (s *server) Run() error {
- engine, err := s.initRouter()
- if err != nil {
- return err
- }
- s.startTask()
- 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))
- if certFile != "" || keyFile != "" {
- logger.Info("web server run https on", listenAddr)
- return engine.RunTLS(listenAddr, certFile, keyFile)
- } else {
- logger.Info("web server run http on", listenAddr)
- return engine.Run(listenAddr)
- }
- }
- func (s *Server) Stop() error {
- s.cancel()
- return s.listener.Close()
- }
|