1
0

web.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package web
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "embed"
  6. "github.com/BurntSushi/toml"
  7. "github.com/gin-contrib/sessions"
  8. "github.com/gin-contrib/sessions/cookie"
  9. "github.com/gin-gonic/gin"
  10. "github.com/nicksnyder/go-i18n/v2/i18n"
  11. "github.com/robfig/cron/v3"
  12. "golang.org/x/text/language"
  13. "html/template"
  14. "io"
  15. "io/fs"
  16. "net"
  17. "net/http"
  18. "os"
  19. "strconv"
  20. "time"
  21. "x-ui/config"
  22. "x-ui/logger"
  23. "x-ui/util/common"
  24. "x-ui/web/controller"
  25. "x-ui/web/service"
  26. )
  27. //go:embed assets/*
  28. var assetsFS embed.FS
  29. //go:embed html/*
  30. var htmlFS embed.FS
  31. //go:embed translation/*
  32. var i18nFS embed.FS
  33. type wrapAssetsFS struct {
  34. embed.FS
  35. }
  36. func (f *wrapAssetsFS) Open(name string) (fs.File, error) {
  37. return f.FS.Open("assets/" + name)
  38. }
  39. type Server struct {
  40. httpServer *http.Server
  41. listener net.Listener
  42. index *controller.IndexController
  43. server *controller.ServerController
  44. xui *controller.XUIController
  45. xrayService service.XrayService
  46. settingService service.SettingService
  47. inboundService service.InboundService
  48. cron *cron.Cron
  49. ctx context.Context
  50. cancel context.CancelFunc
  51. }
  52. func NewServer() *Server {
  53. ctx, cancel := context.WithCancel(context.Background())
  54. return &Server{
  55. ctx: ctx,
  56. cancel: cancel,
  57. }
  58. }
  59. func (s *Server) getHtmlFiles() ([]string, error) {
  60. files := make([]string, 0)
  61. dir, _ := os.Getwd()
  62. err := fs.WalkDir(os.DirFS(dir), "web/html", func(path string, d fs.DirEntry, err error) error {
  63. if err != nil {
  64. return err
  65. }
  66. if d.IsDir() {
  67. return nil
  68. }
  69. files = append(files, path)
  70. return nil
  71. })
  72. if err != nil {
  73. return nil, err
  74. }
  75. return files, nil
  76. }
  77. func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) {
  78. t := template.New("").Funcs(funcMap)
  79. err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error {
  80. if err != nil {
  81. return err
  82. }
  83. if d.IsDir() {
  84. newT, err := t.ParseFS(htmlFS, path+"/*.html")
  85. if err != nil {
  86. // ignore
  87. return nil
  88. }
  89. t = newT
  90. }
  91. return nil
  92. })
  93. if err != nil {
  94. return nil, err
  95. }
  96. return t, nil
  97. }
  98. func (s *Server) initRouter() (*gin.Engine, error) {
  99. if config.IsDebug() {
  100. gin.SetMode(gin.DebugMode)
  101. } else {
  102. gin.DefaultWriter = io.Discard
  103. gin.DefaultErrorWriter = io.Discard
  104. gin.SetMode(gin.ReleaseMode)
  105. }
  106. engine := gin.Default()
  107. secret, err := s.settingService.GetSecret()
  108. if err != nil {
  109. return nil, err
  110. }
  111. basePath, err := s.settingService.GetBasePath()
  112. if err != nil {
  113. return nil, err
  114. }
  115. store := cookie.NewStore(secret)
  116. engine.Use(sessions.Sessions("session", store))
  117. engine.Use(func(c *gin.Context) {
  118. c.Set("base_path", basePath)
  119. })
  120. err = s.initI18n(engine)
  121. if err != nil {
  122. return nil, err
  123. }
  124. if config.IsDebug() {
  125. // for develop
  126. files, err := s.getHtmlFiles()
  127. if err != nil {
  128. return nil, err
  129. }
  130. engine.LoadHTMLFiles(files...)
  131. engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets")))
  132. } else {
  133. // for prod
  134. t, err := s.getHtmlTemplate(engine.FuncMap)
  135. if err != nil {
  136. return nil, err
  137. }
  138. engine.SetHTMLTemplate(t)
  139. engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS}))
  140. }
  141. g := engine.Group(basePath)
  142. s.index = controller.NewIndexController(g)
  143. s.server = controller.NewServerController(g)
  144. s.xui = controller.NewXUIController(g)
  145. return engine, nil
  146. }
  147. func (s *Server) initI18n(engine *gin.Engine) error {
  148. bundle := i18n.NewBundle(language.SimplifiedChinese)
  149. bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
  150. err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error {
  151. if err != nil {
  152. return err
  153. }
  154. if d.IsDir() {
  155. return nil
  156. }
  157. data, err := i18nFS.ReadFile(path)
  158. if err != nil {
  159. return err
  160. }
  161. _, err = bundle.ParseMessageFileBytes(data, path)
  162. return err
  163. })
  164. if err != nil {
  165. return err
  166. }
  167. findI18nParamNames := func(key string) []string {
  168. names := make([]string, 0)
  169. keyLen := len(key)
  170. for i := 0; i < keyLen-1; i++ {
  171. if key[i:i+2] == "{{" { // 判断开头 "{{"
  172. j := i + 2
  173. isFind := false
  174. for ; j < keyLen-1; j++ {
  175. if key[j:j+2] == "}}" { // 结尾 "}}"
  176. isFind = true
  177. break
  178. }
  179. }
  180. if isFind {
  181. names = append(names, key[i+3:j])
  182. }
  183. }
  184. }
  185. return names
  186. }
  187. var localizer *i18n.Localizer
  188. engine.FuncMap["i18n"] = func(key string, params ...string) (string, error) {
  189. names := findI18nParamNames(key)
  190. if len(names) != len(params) {
  191. return "", common.NewError("find names:", names, "---------- params:", params, "---------- num not equal")
  192. }
  193. templateData := map[string]interface{}{}
  194. for i := range names {
  195. templateData[names[i]] = params[i]
  196. }
  197. return localizer.Localize(&i18n.LocalizeConfig{
  198. MessageID: key,
  199. TemplateData: templateData,
  200. })
  201. }
  202. engine.Use(func(c *gin.Context) {
  203. accept := c.GetHeader("Accept-Language")
  204. localizer = i18n.NewLocalizer(bundle, accept)
  205. c.Set("localizer", localizer)
  206. c.Next()
  207. })
  208. return nil
  209. }
  210. func (s *Server) startTask() {
  211. err := s.xrayService.RestartXray()
  212. if err != nil {
  213. logger.Warning("start xray failed:", err)
  214. }
  215. var checkTime = 0
  216. // 每 30 秒检查一次 xray 是否在运行
  217. s.cron.AddFunc("@every 30s", func() {
  218. if s.xrayService.IsXrayRunning() {
  219. checkTime = 0
  220. return
  221. }
  222. checkTime++
  223. if checkTime < 2 {
  224. return
  225. }
  226. s.xrayService.SetToNeedRestart()
  227. })
  228. go func() {
  229. time.Sleep(time.Second * 5)
  230. // 每 10 秒统计一次流量,首次启动延迟 5 秒,与重启 xray 的时间错开
  231. s.cron.AddFunc("@every 10s", func() {
  232. if !s.xrayService.IsXrayRunning() {
  233. return
  234. }
  235. traffics, err := s.xrayService.GetXrayTraffic()
  236. if err != nil {
  237. logger.Warning("get xray traffic failed:", err)
  238. return
  239. }
  240. err = s.inboundService.AddTraffic(traffics)
  241. if err != nil {
  242. logger.Warning("add traffic failed:", err)
  243. }
  244. })
  245. }()
  246. // 每 30 秒检查一次 inbound 流量超出情况
  247. s.cron.AddFunc("@every 30s", func() {
  248. count, err := s.inboundService.DisableInvalidInbounds()
  249. if err != nil {
  250. logger.Warning("disable invalid inbounds err:", err)
  251. } else if count > 0 {
  252. logger.Debugf("disabled %v inbounds", count)
  253. s.xrayService.SetToNeedRestart()
  254. }
  255. })
  256. }
  257. func (s *Server) Start() (err error) {
  258. defer func() {
  259. if err != nil {
  260. s.Stop()
  261. }
  262. }()
  263. loc, err := s.settingService.GetTimeLocation()
  264. if err != nil {
  265. return err
  266. }
  267. s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds())
  268. s.cron.Start()
  269. engine, err := s.initRouter()
  270. if err != nil {
  271. return err
  272. }
  273. certFile, err := s.settingService.GetCertFile()
  274. if err != nil {
  275. return err
  276. }
  277. keyFile, err := s.settingService.GetKeyFile()
  278. if err != nil {
  279. return err
  280. }
  281. listen, err := s.settingService.GetListen()
  282. if err != nil {
  283. return err
  284. }
  285. port, err := s.settingService.GetPort()
  286. if err != nil {
  287. return err
  288. }
  289. listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
  290. var listener net.Listener
  291. if certFile != "" || keyFile != "" {
  292. var cert tls.Certificate
  293. cert, err = tls.LoadX509KeyPair(certFile, keyFile)
  294. if err != nil {
  295. return err
  296. }
  297. c := &tls.Config{
  298. Certificates: []tls.Certificate{cert},
  299. }
  300. listener, err = tls.Listen("tcp", listenAddr, c)
  301. } else {
  302. listener, err = net.Listen("tcp", listenAddr)
  303. }
  304. if err != nil {
  305. return err
  306. }
  307. if certFile != "" || keyFile != "" {
  308. logger.Info("web server run https on", listener.Addr())
  309. } else {
  310. logger.Info("web server run http on", listener.Addr())
  311. }
  312. s.listener = listener
  313. s.startTask()
  314. s.httpServer = &http.Server{
  315. Handler: engine,
  316. }
  317. go s.httpServer.Serve(listener)
  318. return nil
  319. }
  320. func (s *Server) Stop() error {
  321. s.cancel()
  322. s.xrayService.StopXray()
  323. if s.cron != nil {
  324. s.cron.Stop()
  325. }
  326. var err1 error
  327. var err2 error
  328. if s.httpServer != nil {
  329. err1 = s.httpServer.Shutdown(s.ctx)
  330. }
  331. if s.listener != nil {
  332. err2 = s.listener.Close()
  333. }
  334. return common.Combine(err1, err2)
  335. }
  336. func (s *Server) GetCtx() context.Context {
  337. return s.ctx
  338. }
  339. func (s *Server) GetCron() *cron.Cron {
  340. return s.cron
  341. }