mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 03:05:02 +03:00
3526a65f5d
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.
65 lines
1.7 KiB
Go
65 lines
1.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io/fs"
|
|
"net/http"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// GzipStatic serves a static route's "*filepath" from a gzip copy made once per
|
|
// file, so a stalled download holds shared bytes instead of its own compressor.
|
|
func GzipStatic(files fs.FS) gin.HandlerFunc {
|
|
var compressed sync.Map // name -> func() []byte
|
|
return func(c *gin.Context) {
|
|
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
|
|
return
|
|
}
|
|
c.Writer.Header().Add("Vary", "Accept-Encoding")
|
|
if !strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") {
|
|
return
|
|
}
|
|
name := strings.TrimPrefix(path.Clean("/"+c.Param("filepath")), "/")
|
|
// Only names that exist get an entry, so requests cannot grow the map.
|
|
info, err := fs.Stat(files, name)
|
|
if err != nil || info.IsDir() {
|
|
return
|
|
}
|
|
once, _ := compressed.LoadOrStore(name, sync.OnceValue(func() []byte {
|
|
return gzipFile(files, name)
|
|
}))
|
|
body := once.(func() []byte)()
|
|
if body == nil {
|
|
return
|
|
}
|
|
c.Header("Content-Encoding", "gzip")
|
|
// ServeContent leaves Content-Length unset once Content-Encoding is present.
|
|
c.Header("Content-Length", strconv.Itoa(len(body)))
|
|
http.ServeContent(c.Writer, c.Request, name, info.ModTime(), bytes.NewReader(body))
|
|
c.Abort()
|
|
}
|
|
}
|
|
|
|
// gzipFile returns the file compressed, or nil to leave it to the static route.
|
|
func gzipFile(files fs.FS, name string) []byte {
|
|
raw, err := fs.ReadFile(files, name)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var buf bytes.Buffer
|
|
zw, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
|
|
if _, err := zw.Write(raw); err != nil {
|
|
return nil
|
|
}
|
|
if err := zw.Close(); err != nil {
|
|
return nil
|
|
}
|
|
return buf.Bytes()
|
|
}
|