mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(web): bound the panel's open connections and share asset gzip
The engine-wide gzip middleware gave every in-flight request its own compressor of about 1 MiB, and the public /assets bundles let an anonymous client keep a handler blocked in Write until WriteTimeout, so a thousand stalled downloads held a gigabyte. The listener itself took any number of connections, each holding buffers and a goroutine before any login. The bundles are now served from a gzip copy made once per file, with the assets prefix left out of the per-request middleware, and the panel listener closes a new connection once 2048 are open or its source (an IPv4 address or IPv6 /64) holds 256; a loopback reverse proxy counts only toward the total.
This commit is contained in:
+11
-10
@@ -66,16 +66,14 @@ type cronPanicLogger struct{}
|
||||
|
||||
func (cronPanicLogger) Printf(format string, args ...any) { logger.Errorf(format, args...) }
|
||||
|
||||
// wrapDistFS adapts the embedded `dist/` directory so it can be mounted
|
||||
// as the panel's `/assets/` static route. Vite emits its bundled JS/CSS
|
||||
// under `dist/assets/`; serving the FS rooted at `dist/assets` makes
|
||||
// `/assets/<hash>.js` URLs resolve directly.
|
||||
// wrapDistFS serves the embedded `dist/assets` tree (Vite's bundled JS/CSS) as the panel's
|
||||
// `/assets/` route; a named field keeps embed.FS's ReadFile, rooted at `dist`, unpromoted.
|
||||
type wrapDistFS struct {
|
||||
embed.FS
|
||||
dist embed.FS
|
||||
}
|
||||
|
||||
func (f *wrapDistFS) Open(name string) (fs.File, error) {
|
||||
file, err := f.FS.Open("dist/assets/" + name)
|
||||
file, err := f.dist.Open("dist/assets/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -205,8 +203,10 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engine.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
assetsBasePath := basePath + "assets/"
|
||||
// The bundles are served below from a gzip copy made once per file: compressing them
|
||||
// per request lets every stalled download pin its own compressor until WriteTimeout.
|
||||
engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{assetsBasePath})))
|
||||
|
||||
store := cookie.NewStore(secret)
|
||||
// Configure default session cookie options, including expiration (MaxAge)
|
||||
@@ -244,11 +244,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
// so the Vite watcher's incremental rebuilds show up without
|
||||
// restarting the binary; in prod we serve the embedded dist FS
|
||||
// rooted at `dist/assets/`.
|
||||
var assetsFS fs.FS = &wrapDistFS{dist: distFS}
|
||||
if config.IsDebug() {
|
||||
engine.StaticFS(basePath+"assets", http.FS(os.DirFS("internal/web/dist/assets")))
|
||||
} else {
|
||||
engine.StaticFS(basePath+"assets", http.FS(&wrapDistFS{FS: distFS}))
|
||||
assetsFS = os.DirFS("internal/web/dist/assets")
|
||||
}
|
||||
engine.Group(basePath+"assets", middleware.GzipStatic(assetsFS)).StaticFS("/", http.FS(assetsFS))
|
||||
|
||||
// Hand the embedded `dist/` filesystem to the controller package
|
||||
// before any HTML-serving controller is constructed. Phase 8
|
||||
@@ -606,6 +606,7 @@ func (s *Server) start(restartXray bool, startTgBot bool) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener = network.NewLimitListener(listener, network.MaxPanelConns, network.MaxPanelConnsPerSource)
|
||||
if certFile != "" || keyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err == nil {
|
||||
|
||||
Reference in New Issue
Block a user