diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index deba082c..cac2707c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -529,7 +529,6 @@ jobs: env: VERSION: ${{ needs.get-release.outputs.version }} run: | - VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified cd ui/flutter flutter build web --no-web-resources-cdn cd ../../ diff --git a/pkg/rest/gizp_middleware.go b/pkg/rest/gizp_middleware.go new file mode 100644 index 00000000..8170db7b --- /dev/null +++ b/pkg/rest/gizp_middleware.go @@ -0,0 +1,34 @@ +package rest + +import ( + "compress/gzip" + "io" + "net/http" + "strings" +) + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (g gzipResponseWriter) Write(b []byte) (int, error) { + return g.Writer.Write(b) +} + +func gzipMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + next.ServeHTTP(w, r) + return + } + + w.Header().Set("Content-Encoding", "gzip") + w.Header().Add("Vary", "Accept-Encoding") + + gz := gzip.NewWriter(w) + defer gz.Close() + + next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r) + }) +} diff --git a/pkg/rest/server.go b/pkg/rest/server.go index 9c0836d4..1e4a10ec 100644 --- a/pkg/rest/server.go +++ b/pkg/rest/server.go @@ -10,12 +10,14 @@ import ( "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/pkg/errors" + "io/fs" "net" "net/http" "os" "path/filepath" "regexp" "strings" + "time" ) var ( @@ -126,7 +128,7 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error if startCfg.WebEnable { r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem))) r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem))) - r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS))) + r.PathPrefix("/").Handler(gzipMiddleware(http.FileServer(newEmbedCacheFileSystem(http.FS(startCfg.WebFS))))) } if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) { @@ -222,6 +224,68 @@ func (e *extensionFileSystem) Open(name string) (http.File, error) { return os.Open(filepath.Join(extensionPath, path)) } +type embedCacheFileSystem struct { + fs http.FileSystem + lastModTime time.Time +} + +func newEmbedCacheFileSystem(fs http.FileSystem) *embedCacheFileSystem { + efs := &embedCacheFileSystem{ + fs: fs, + lastModTime: time.Now(), + } + + exe, err := os.Executable() + if err != nil { + return efs + } + + fi, err := os.Stat(exe) + if err != nil { + return efs + } + + efs.lastModTime = fi.ModTime() + return efs +} + +func (e *embedCacheFileSystem) Open(name string) (http.File, error) { + file, err := e.fs.Open(name) + if err != nil { + return nil, err + } + + return &embedFile{ + File: file, + lastModTime: e.lastModTime, + }, nil +} + +type embedFile struct { + http.File + lastModTime time.Time +} + +type embedFileInfo struct { + fs.FileInfo + lastModTime time.Time +} + +func (e *embedFileInfo) ModTime() time.Time { + return e.lastModTime +} + +func (e *embedFile) Stat() (fs.FileInfo, error) { + fi, err := e.File.Stat() + if err != nil { + return nil, err + } + return &embedFileInfo{ + FileInfo: fi, + lastModTime: e.lastModTime, + }, nil +} + func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool { if err := json.NewDecoder(r.Body).Decode(v); err != nil { WriteJson(w, model.NewErrorResult(err.Error())) diff --git a/pkg/rest/server_test.go b/pkg/rest/server_test.go index 930d9817..9dee79f3 100644 --- a/pkg/rest/server_test.go +++ b/pkg/rest/server_test.go @@ -14,6 +14,7 @@ import ( "net" "net/http" "os" + "path/filepath" "reflect" "sync" "testing" @@ -478,6 +479,66 @@ func TestFsExtensionFail(t *testing.T) { }) } +func TestWebFsEnhance(t *testing.T) { + indexHtml := ` + + +
+ +