Files
new-api/middleware/frontend_static_test.go
CaIon 8ca26d81df fix(web): stop frontend build files from exhausting the web rate limit
GLOBAL_WEB_RATE_LIMIT (120 requests per 180 s per IP) counted every file
served from the frontend build. Since provider icons became lazy chunks,
one cold session sends about 90 static requests, so a second user behind
the same IP gets 429 on route chunks and the page fails to load.

Files that exist in the build now count against a separate static
limiter (GLOBAL_STATIC_RATE_LIMIT_ENABLE, GLOBAL_STATIC_RATE_LIMIT,
GLOBAL_STATIC_RATE_LIMIT_DURATION), disabled by default. Pages and
unknown paths still count against the web limit.

The gzip middleware also recompressed every static response, about
120 ms of CPU per request for the 4.8 MB main bundle. Build files are
embedded and never change, so each one is now served from a gzip
encoding computed once per process. Pages and unknown API paths still
go through the gzip middleware.
2026-10-07 21:26:34 +08:00

107 lines
3.8 KiB
Go

package middleware
import (
"bytes"
"compress/gzip"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type countingFrontendFS struct {
static.ServeFileSystem
opens atomic.Int32
}
func (f *countingFrontendFS) Open(name string) (http.File, error) {
f.opens.Add(1)
return f.ServeFileSystem.Open(name)
}
func performFrontendRequest(router http.Handler, method, target, acceptEncoding string) *httptest.ResponseRecorder {
recorder := httptest.NewRecorder()
request := httptest.NewRequest(method, target, nil)
if acceptEncoding != "" {
request.Header.Set("Accept-Encoding", acceptEncoding)
}
router.ServeHTTP(recorder, request)
return recorder
}
func TestServeFrontendFilesCompressesEachFileOnce(t *testing.T) {
gin.SetMode(gin.TestMode)
frontendDir := t.TempDir()
script := []byte(strings.Repeat("export const answer = 42;\n", 400))
image := []byte("\x89PNG\r\n\x1a\nnot-really-an-image")
require.NoError(t, os.MkdirAll(filepath.Join(frontendDir, "static", "js"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(frontendDir, "static", "js", "app.js"), script, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(frontendDir, "logo.png"), image, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(frontendDir, "index.html"), []byte("<html></html>"), 0o644))
frontendFS := &countingFrontendFS{ServeFileSystem: static.LocalFile(frontendDir, false)}
router := gin.New()
router.NoRoute(ServeFrontendFiles(frontendFS), func(c *gin.Context) {
c.String(http.StatusTeapot, "fallback")
})
plain := performFrontendRequest(router, http.MethodGet, "/static/js/app.js", "")
require.Equal(t, http.StatusOK, plain.Code)
assert.Empty(t, plain.Header().Get("Content-Encoding"))
assert.Equal(t, script, plain.Body.Bytes())
frontendFS.opens.Store(0)
responses := make([]*httptest.ResponseRecorder, 8)
var wg sync.WaitGroup
for i := range responses {
wg.Go(func() {
responses[i] = performFrontendRequest(router, http.MethodGet, "/static/js/app.js", "gzip, deflate, br")
})
}
wg.Wait()
for _, response := range responses {
require.Equal(t, http.StatusOK, response.Code)
assert.Equal(t, "gzip", response.Header().Get("Content-Encoding"))
assert.Equal(t, "Accept-Encoding", response.Header().Get("Vary"))
assert.Equal(t, plain.Header().Get("Content-Type"), response.Header().Get("Content-Type"))
assert.Equal(t, strconv.Itoa(response.Body.Len()), response.Header().Get("Content-Length"))
assert.Equal(t, responses[0].Body.Bytes(), response.Body.Bytes())
}
reader, err := gzip.NewReader(bytes.NewReader(responses[0].Body.Bytes()))
require.NoError(t, err)
decoded, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, script, decoded)
head := performFrontendRequest(router, http.MethodHead, "/static/js/app.js", "gzip")
assert.Equal(t, http.StatusOK, head.Code)
assert.Equal(t, "gzip", head.Header().Get("Content-Encoding"))
assert.Equal(t, strconv.Itoa(responses[0].Body.Len()), head.Header().Get("Content-Length"))
assert.Empty(t, head.Body.Bytes())
assert.Equal(t, int32(1), frontendFS.opens.Load(), "the file must be read and compressed once")
png := performFrontendRequest(router, http.MethodGet, "/logo.png", "gzip")
assert.Equal(t, http.StatusOK, png.Code)
assert.Empty(t, png.Header().Get("Content-Encoding"))
assert.Equal(t, image, png.Body.Bytes())
index := performFrontendRequest(router, http.MethodGet, "/index.html", "gzip")
assert.Equal(t, http.StatusMovedPermanently, index.Code)
assert.Equal(t, "./", index.Header().Get("Location"))
missing := performFrontendRequest(router, http.MethodGet, "/static/js/missing.js", "gzip")
assert.Equal(t, http.StatusTeapot, missing.Code)
assert.Equal(t, "fallback", missing.Body.String())
}