gui: serve static files with gzip/deflate compression
Before this change, the GUI server sent all static files uncompressed, meaning the browser had to download the full size of every JS, CSS, and HTML asset. After this change, the GUI server uses chi's Compress middleware at level 5, which negotiates gzip or deflate encoding based on the client's Accept-Encoding header. This reduces transfer sizes significantly for the web UI assets, for example assets/index-CvfdU_RR.js is 874 KB uncompressed, and 265 KB compressed. This is consistent with how rclone serve http, webdav, and restic already compress their responses.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/rclone/rclone/cmd"
|
"github.com/rclone/rclone/cmd"
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/rc"
|
"github.com/rclone/rclone/fs/rc"
|
||||||
@@ -191,6 +192,7 @@ For more help see [the GUI docs](/gui/).
|
|||||||
if err != nil || spaHandler == nil {
|
if err != nil || spaHandler == nil {
|
||||||
return fmt.Errorf("failed to start GUI handler: %w", err)
|
return fmt.Errorf("failed to start GUI handler: %w", err)
|
||||||
}
|
}
|
||||||
|
guiServer.Router().Use(middleware.Compress(5))
|
||||||
guiServer.Router().Get("/*", spaHandler.ServeHTTP)
|
guiServer.Router().Get("/*", spaHandler.ServeHTTP)
|
||||||
guiServer.Router().Head("/*", spaHandler.ServeHTTP)
|
guiServer.Router().Head("/*", spaHandler.ServeHTTP)
|
||||||
guiServer.Serve()
|
guiServer.Serve()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
"io"
|
"io"
|
||||||
iofs "io/fs"
|
iofs "io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -11,6 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -302,3 +305,37 @@ func TestHandlerSPAFallbackDeepPath(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
assert.Contains(t, string(body), "<div id=\"root\"></div>")
|
assert.Contains(t, string(body), "<div id=\"root\"></div>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlerServesGzip(t *testing.T) {
|
||||||
|
dir := writeTestDir(t)
|
||||||
|
srcFS, cleanup, err := guiSourceFS(dir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = cleanup() })
|
||||||
|
|
||||||
|
h, err := guiHandler(srcFS)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Build a chi router with Compress middleware, mirroring the
|
||||||
|
// production setup in the gui command.
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Use(middleware.Compress(5))
|
||||||
|
r.Get("/*", h.ServeHTTP)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
req.Header.Set("Accept-Encoding", "gzip")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding"),
|
||||||
|
"response should be gzip-encoded when client accepts it")
|
||||||
|
|
||||||
|
// Decompress and verify the content is correct.
|
||||||
|
gr, err := gzip.NewReader(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
body, err := io.ReadAll(gr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, gr.Close())
|
||||||
|
assert.Contains(t, string(body), `<div id="root"></div>`)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user