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:
@@ -0,0 +1,88 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// readCountingFS counts ReadFile calls, i.e. how often a file gets compressed.
|
||||
type readCountingFS struct {
|
||||
fstest.MapFS
|
||||
reads atomic.Int32
|
||||
}
|
||||
|
||||
func (f *readCountingFS) ReadFile(name string) ([]byte, error) {
|
||||
f.reads.Add(1)
|
||||
return f.MapFS.ReadFile(name)
|
||||
}
|
||||
|
||||
func TestGzipStatic(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
raw := strings.Repeat("console.log('bundle');", 4096)
|
||||
files := &readCountingFS{MapFS: fstest.MapFS{
|
||||
"app.js": {Data: []byte(raw)},
|
||||
}}
|
||||
r := gin.New()
|
||||
r.Group("/assets", GzipStatic(files)).StaticFS("/", http.FS(files))
|
||||
|
||||
do := func(method, target, acceptEncoding string) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
req := httptest.NewRequest(method, target, nil)
|
||||
req.Header.Set("Accept-Encoding", acceptEncoding)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
t.Run("gzip downloads share one compression", func(t *testing.T) {
|
||||
for range 3 {
|
||||
w := do(http.MethodGet, "/assets/app.js", "gzip, deflate")
|
||||
if w.Code != http.StatusOK || w.Header().Get("Content-Encoding") != "gzip" || w.Header().Get("Vary") != "Accept-Encoding" {
|
||||
t.Fatalf("got %d, headers %v", w.Code, w.Header())
|
||||
}
|
||||
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "javascript") {
|
||||
t.Errorf("Content-Type = %q, want the bundle's own type", ct)
|
||||
}
|
||||
zr, err := gzip.NewReader(bytes.NewReader(w.Body.Bytes()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if plain, _ := io.ReadAll(zr); string(plain) != raw {
|
||||
t.Fatal("decompressed body differs from the bundle")
|
||||
}
|
||||
}
|
||||
if n := files.reads.Load(); n != 1 {
|
||||
t.Errorf("bundle compressed %d times, want once", n)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HEAD sends headers only", func(t *testing.T) {
|
||||
w := do(http.MethodHead, "/assets/app.js", "gzip")
|
||||
if w.Code != http.StatusOK || w.Body.Len() != 0 || w.Header().Get("Content-Encoding") != "gzip" {
|
||||
t.Errorf("got %d, %d body bytes, headers %v", w.Code, w.Body.Len(), w.Header())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("without Accept-Encoding the static route serves the plain file", func(t *testing.T) {
|
||||
w := do(http.MethodGet, "/assets/app.js", "")
|
||||
if w.Code != http.StatusOK || w.Header().Get("Content-Encoding") != "" || w.Body.String() != raw {
|
||||
t.Errorf("got %d, Content-Encoding %q, %d body bytes", w.Code, w.Header().Get("Content-Encoding"), w.Body.Len())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown name is left to the static route", func(t *testing.T) {
|
||||
w := do(http.MethodGet, "/assets/none.js", "gzip")
|
||||
if w.Code != http.StatusNotFound || w.Header().Get("Content-Encoding") != "" {
|
||||
t.Errorf("got %d, Content-Encoding %q, want 404 plain", w.Code, w.Header().Get("Content-Encoding"))
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user