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,89 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Any per-request compressor here is pinned for as long as an anonymous client
|
||||
// stalls its download, so the bundles must come out as one shared gzip copy.
|
||||
func TestAssetsServedFromSharedGzipCopy(t *testing.T) {
|
||||
entries, err := fs.ReadDir(distFS, "dist/assets")
|
||||
if err != nil || len(entries) == 0 {
|
||||
t.Skip("no built frontend bundles embedded (dist stub)")
|
||||
}
|
||||
name := ""
|
||||
for _, e := range entries {
|
||||
if strings.HasSuffix(e.Name(), ".js") {
|
||||
name = e.Name()
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
t.Skip("no .js bundle embedded")
|
||||
}
|
||||
raw, err := fs.ReadFile(distFS, "dist/assets/"+name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
engine := newTestEngine(t)
|
||||
|
||||
get := func(acceptEncoding string) *http.Response {
|
||||
t.Helper()
|
||||
req := httptest.NewRequest(http.MethodGet, "/assets/"+name, nil)
|
||||
req.Header.Set("Accept-Encoding", acceptEncoding)
|
||||
w := httptest.NewRecorder()
|
||||
engine.ServeHTTP(w, req)
|
||||
return w.Result()
|
||||
}
|
||||
|
||||
for range 2 {
|
||||
res := get("gzip")
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get("Content-Encoding") != "gzip" {
|
||||
t.Fatalf("gzip download: got %d, Content-Encoding %q", res.StatusCode, res.Header.Get("Content-Encoding"))
|
||||
}
|
||||
// A precompressed copy is sent with its length; a streamed compressor can't know it.
|
||||
if got := res.Header.Get("Content-Length"); got != strconv.Itoa(len(body)) {
|
||||
t.Errorf("Content-Length = %q, want %d: bundle was compressed per request", got, len(body))
|
||||
}
|
||||
if res.Header.Get("Last-Modified") == "" {
|
||||
t.Error("Last-Modified missing: assets FS no longer stamps startTime")
|
||||
}
|
||||
zr, err := gzip.NewReader(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if plain, _ := io.ReadAll(zr); !bytes.Equal(plain, raw) {
|
||||
t.Fatal("gzip download decompresses to something other than the bundle")
|
||||
}
|
||||
}
|
||||
|
||||
res := get("")
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get("Content-Encoding") != "" || !bytes.Equal(body, raw) {
|
||||
t.Fatalf("plain download: got %d, Content-Encoding %q, %d bytes", res.StatusCode, res.Header.Get("Content-Encoding"), len(body))
|
||||
}
|
||||
}
|
||||
|
||||
// The assets route never listed its directory; serving the bundles from a shared copy
|
||||
// must not turn that 404 into a 500.
|
||||
func TestAssetsDirectoryIsNotFound(t *testing.T) {
|
||||
engine := newTestEngine(t)
|
||||
for _, encoding := range []string{"", "gzip"} {
|
||||
req := httptest.NewRequest(http.MethodGet, "/assets/", nil)
|
||||
req.Header.Set("Accept-Encoding", encoding)
|
||||
w := httptest.NewRecorder()
|
||||
engine.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("GET /assets/ with Accept-Encoding %q = %d, want 404", encoding, w.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user