mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +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.
90 lines
2.7 KiB
Go
90 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|