diff --git a/config/config.go b/config/config.go index 9a5ec5a..ae5820d 100644 --- a/config/config.go +++ b/config/config.go @@ -43,6 +43,7 @@ type Config struct { WebHookEndpoint string `json:"webhook_endpoint" envconfig:"WEBHOOK_ENDPOINT"` WebHookSecret string `json:"webhook_secret" envconfig:"WEBHOOK_SECRET"` Debug bool `json:"debug" envconfig:"DEBUG"` + Cache bool `json:"cache" envconfig:"CACHE"` Watch bool `json:"watch" envconfig:"WATCH"` LogData bool `json:"log_data" envconfig:"LOG_DATA"` diff --git a/controllers/routes.go b/controllers/routes.go index 7b03ec0..d69c3e9 100644 --- a/controllers/routes.go +++ b/controllers/routes.go @@ -29,7 +29,7 @@ func Register(server server.Server, cfg config.Config) { Level: compress.LevelBestSpeed, })) server.Server.Use(ASSETS_URL, etag.New()) - server.Server.Use(ASSETS_URL, middleware.StaticHandler(&views.StaticFS)) + server.Server.Use(ASSETS_URL, middleware.StaticHandler(&views.StaticFS, cfg.Debug)) server.Server.Use(func(ctx *fiber.Ctx) error { ctx.Locals("cfg", cfg) diff --git a/helpers/middleware/etag.go b/helpers/middleware/etag.go new file mode 100644 index 0000000..59b9c56 --- /dev/null +++ b/helpers/middleware/etag.go @@ -0,0 +1,141 @@ +package middleware + +import ( + "bytes" + "hash/crc32" + + "github.com/gofiber/fiber/v2" + + "github.com/valyala/bytebufferpool" +) + +// Config defines the config for middleware. +type Config struct { + // Weak indicates that a weak validator is used. Weak etags are easy + // to generate, but are far less useful for comparisons. Strong + // validators are ideal for comparisons but can be very difficult + // to generate efficiently. Weak ETag values of two representations + // of the same resources might be semantically equivalent, but not + // byte-for-byte identical. This means weak etags prevent caching + // when byte range requests are used, but strong etags mean range + // requests can still be cached. + Weak bool + + // Next defines a function to skip this middleware when returned true. + // + // Optional. Default: nil + Next func(c *fiber.Ctx) bool +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + Weak: false, + Next: nil, +} + +// Helper function to set default values +func configDefault(config ...Config) Config { + // Return default config if nothing provided + if len(config) < 1 { + return ConfigDefault + } + + // Override default config + cfg := config[0] + + // Set default values + + return cfg +} + +func New() fiber.Handler { + var ( + normalizedHeaderETag = []byte("Etag") + weakPrefix = []byte("W/") + ) + + const crcPol = 0xD5828281 + crc32q := crc32.MakeTable(crcPol) + + // Return new handler + return func(c *fiber.Ctx) error { + // Return err if next handler returns one + if err := c.Next(); err != nil { + return err + } + + // Don't generate ETags for invalid responses + if c.Response().StatusCode() != fiber.StatusOK { + return nil + } + body := c.Response().Body() + // Skips ETag if no response body is present + if len(body) == 0 { + return nil + } + // Skip ETag if header is already present + if c.Response().Header.PeekBytes(normalizedHeaderETag) != nil { + return nil + } + + // Generate ETag for response + bb := bytebufferpool.Get() + defer bytebufferpool.Put(bb) + + _ = bb.WriteByte('"') //nolint:errcheck // This will never fail + bb.B = appendUint(bb.Bytes(), uint32(len(body))) + _ = bb.WriteByte('-') //nolint:errcheck // This will never fail + bb.B = appendUint(bb.Bytes(), crc32.Checksum(body, crc32q)) + _ = bb.WriteByte('"') //nolint:errcheck // This will never fail + + etag := bb.Bytes() + + // Get ETag header from request + clientEtag := c.Request().Header.Peek(fiber.HeaderIfNoneMatch) + + // Check if client's ETag is weak + if bytes.HasPrefix(clientEtag, weakPrefix) { + // Check if server's ETag is weak + if bytes.Equal(clientEtag[2:], etag) || bytes.Equal(clientEtag[2:], etag[2:]) { + // W/1 == 1 || W/1 == W/1 + c.Context().ResetBody() + + return c.SendStatus(fiber.StatusNotModified) + } + // W/1 != W/2 || W/1 != 2 + c.Response().Header.SetCanonical(normalizedHeaderETag, etag) + + return nil + } + + if bytes.Contains(clientEtag, etag) { + // 1 == 1 + c.Context().ResetBody() + + return c.SendStatus(fiber.StatusNotModified) + } + // 1 != 2 + c.Response().Header.SetCanonical(normalizedHeaderETag, etag) + + return nil + } +} + +// appendUint appends n to dst and returns the extended dst. +func appendUint(dst []byte, n uint32) []byte { + var b [20]byte + buf := b[:] + i := len(buf) + var q uint32 + for n >= 10 { + i-- + q = n / 10 + buf[i] = '0' + byte(n-q*10) + n = q + } + i-- + buf[i] = '0' + byte(n) + + dst = append(dst, buf[i:]...) + return dst +} diff --git a/helpers/middleware/filesystem/filesystem.go b/helpers/middleware/filesystem/filesystem.go new file mode 100644 index 0000000..b6c7365 --- /dev/null +++ b/helpers/middleware/filesystem/filesystem.go @@ -0,0 +1,298 @@ +package filesystem + +import ( + "errors" + "fmt" + "io/fs" + "net/http" + "strconv" + "strings" + "sync" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/utils" +) + +// Config defines the config for middleware. +type Config struct { + // Next defines a function to skip this middleware when returned true. + // + // Optional. Default: nil + Next func(c *fiber.Ctx) bool + + // Root is a FileSystem that provides access + // to a collection of files and directories. + // + // Required. Default: nil + Root http.FileSystem `json:"-"` + + // PathPrefix defines a prefix to be added to a filepath when + // reading a file from the FileSystem. + // + // Use when using Go 1.16 embed.FS + // + // Optional. Default "" + PathPrefix string `json:"path_prefix"` + + // Enable directory browsing. + // + // Optional. Default: false + Browse bool `json:"browse"` + + // Index file for serving a directory. + // + // Optional. Default: "index.html" + Index string `json:"index"` + + // The value for the Cache-Control HTTP-header + // that is set on the file response. MaxAge is defined in seconds. + // + // Optional. Default value 0. + MaxAge int `json:"max_age"` + + // File to return if path is not found. Useful for SPA's. + // + // Optional. Default: "" + NotFoundFile string `json:"not_found_file"` + + // The value for the Content-Type HTTP-header + // that is set on the file response + // + // Optional. Default: "" + ContentTypeCharset string `json:"content_type_charset"` + + // The value for the Content-Type HTTP-header + // that is set on the file response + // + // Optional. Default: true + Dev bool `json:"dev"` +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + Next: nil, + Root: nil, + PathPrefix: "", + Browse: false, + Index: "/index.html", + MaxAge: 0, + ContentTypeCharset: "", + Dev: true, +} + +// New creates a new middleware handler. +// +// filesystem does not handle url encoded values (for example spaces) +// on it's own. If you need that functionality, set "UnescapePath" +// in fiber.Config +func New(config ...Config) fiber.Handler { + // Set default config + cfg := ConfigDefault + + // Override config if provided + if len(config) > 0 { + cfg = config[0] + + // Set default values + if cfg.Index == "" { + cfg.Index = ConfigDefault.Index + } + if !strings.HasPrefix(cfg.Index, "/") { + cfg.Index = "/" + cfg.Index + } + if cfg.NotFoundFile != "" && !strings.HasPrefix(cfg.NotFoundFile, "/") { + cfg.NotFoundFile = "/" + cfg.NotFoundFile + } + } + + if cfg.Root == nil { + panic("filesystem: Root cannot be nil") + } + + if cfg.PathPrefix != "" && !strings.HasPrefix(cfg.PathPrefix, "/") { + cfg.PathPrefix = "/" + cfg.PathPrefix + } + + var once sync.Once + var prefix string + cacheControlStr := "public, max-age=" + strconv.Itoa(cfg.MaxAge) + + if cfg.Dev { + cacheControlStr = "no-cache, must-revalidate" + } + + // Return new handler + return func(c *fiber.Ctx) error { + // Don't execute middleware if Next returns true + if cfg.Next != nil && cfg.Next(c) { + return c.Next() + } + + method := c.Method() + + // We only serve static assets on GET or HEAD methods + if method != fiber.MethodGet && method != fiber.MethodHead { + return c.Next() + } + + // Set prefix once + once.Do(func() { + prefix = c.Route().Path + }) + + // Strip prefix + path := strings.TrimPrefix(c.Path(), prefix) + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + // Add PathPrefix + if cfg.PathPrefix != "" { + // PathPrefix already has a "/" prefix + path = cfg.PathPrefix + path + } + + if len(path) > 1 { + path = utils.TrimRight(path, '/') + } + file, err := cfg.Root.Open(path) + if err != nil && errors.Is(err, fs.ErrNotExist) && cfg.NotFoundFile != "" { + file, err = cfg.Root.Open(cfg.NotFoundFile) + } + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return c.Status(fiber.StatusNotFound).Next() + } + return fmt.Errorf("failed to open: %w", err) + } + + stat, err := file.Stat() + if err != nil { + return fmt.Errorf("failed to stat: %w", err) + } + + // Serve index if path is directory + if stat.IsDir() { + indexPath := utils.TrimRight(path, '/') + cfg.Index + index, err := cfg.Root.Open(indexPath) + if err == nil { + indexStat, err := index.Stat() + if err == nil { + file = index + stat = indexStat + } + } + } + + // Browse directory if no index found and browsing is enabled + if stat.IsDir() { + if cfg.Browse { + return dirList(c, file) + } + return fiber.ErrForbidden + } + + c.Status(fiber.StatusOK) + + modTime := stat.ModTime() + contentLength := int(stat.Size()) + + // Set Content Type header + if cfg.ContentTypeCharset == "" { + c.Type(getFileExtension(stat.Name())) + } else { + c.Type(getFileExtension(stat.Name()), cfg.ContentTypeCharset) + } + + // Set Last Modified header + if !modTime.IsZero() { + c.Set(fiber.HeaderLastModified, modTime.UTC().Format(http.TimeFormat)) + } + + if method == fiber.MethodGet { + if cfg.MaxAge > 0 { + c.Set(fiber.HeaderCacheControl, cacheControlStr) + } + c.Response().SetBodyStream(file, contentLength) + return nil + } + if method == fiber.MethodHead { + c.Request().ResetBody() + // Fasthttp should skipbody by default if HEAD? + c.Response().SkipBody = true + c.Response().Header.SetContentLength(contentLength) + if err := file.Close(); err != nil { + return fmt.Errorf("failed to close: %w", err) + } + return nil + } + + return c.Next() + } +} + +// SendFile serves a file from an HTTP file system at the specified path. +// It handles content serving, sets appropriate headers, and returns errors when needed. +// Usage: err := SendFile(ctx, fs, "/path/to/file.txt") +func SendFile(c *fiber.Ctx, filesystem http.FileSystem, path string) error { + file, err := filesystem.Open(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return fiber.ErrNotFound + } + return fmt.Errorf("failed to open: %w", err) + } + + stat, err := file.Stat() + if err != nil { + return fmt.Errorf("failed to stat: %w", err) + } + + // Serve index if path is directory + if stat.IsDir() { + indexPath := utils.TrimRight(path, '/') + ConfigDefault.Index + index, err := filesystem.Open(indexPath) + if err == nil { + indexStat, err := index.Stat() + if err == nil { + file = index + stat = indexStat + } + } + } + + // Return forbidden if no index found + if stat.IsDir() { + return fiber.ErrForbidden + } + + c.Status(fiber.StatusOK) + + modTime := stat.ModTime() + contentLength := int(stat.Size()) + + // Set Content Type header + c.Type(getFileExtension(stat.Name())) + + // Set Last Modified header + if !modTime.IsZero() { + c.Set(fiber.HeaderLastModified, modTime.UTC().Format(http.TimeFormat)) + } + + method := c.Method() + if method == fiber.MethodGet { + c.Response().SetBodyStream(file, contentLength) + return nil + } + if method == fiber.MethodHead { + c.Request().ResetBody() + // Fasthttp should skipbody by default if HEAD? + c.Response().SkipBody = true + c.Response().Header.SetContentLength(contentLength) + if err := file.Close(); err != nil { + return fmt.Errorf("failed to close: %w", err) + } + return nil + } + + return nil +} diff --git a/helpers/middleware/filesystem/filesystem_test.go b/helpers/middleware/filesystem/filesystem_test.go new file mode 100644 index 0000000..4c646fd --- /dev/null +++ b/helpers/middleware/filesystem/filesystem_test.go @@ -0,0 +1,235 @@ +//nolint:bodyclose // Much easier to just ignore memory leaks in tests +package filesystem + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/utils" +) + +// go test -run Test_FileSystem +func Test_FileSystem(t *testing.T) { + t.Parallel() + app := fiber.New() + + app.Use("/test", New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + })) + + app.Use("/dir", New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + Browse: true, + })) + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("Hello, World!") + }) + + app.Use("/spatest", New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + Index: "index.html", + NotFoundFile: "index.html", + })) + + app.Use("/prefix", New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + PathPrefix: "img", + })) + + tests := []struct { + name string + url string + statusCode int + contentType string + modifiedTime string + }{ + { + name: "Should be returns status 200 with suitable content-type", + url: "/test/index.html", + statusCode: 200, + contentType: "text/html", + }, + { + name: "Should be returns status 200 with suitable content-type", + url: "/test", + statusCode: 200, + contentType: "text/html", + }, + { + name: "Should be returns status 200 with suitable content-type", + url: "/test/css/style.css", + statusCode: 200, + contentType: "text/css", + }, + { + name: "Should be returns status 404", + url: "/test/nofile.js", + statusCode: 404, + }, + { + name: "Should be returns status 404", + url: "/test/nofile", + statusCode: 404, + }, + { + name: "Should be returns status 200", + url: "/", + statusCode: 200, + contentType: "text/plain; charset=utf-8", + }, + { + name: "Should be returns status 403", + url: "/test/img", + statusCode: 403, + }, + { + name: "Should list the directory contents", + url: "/dir/img", + statusCode: 200, + contentType: "text/html", + }, + { + name: "Should list the directory contents", + url: "/dir/img/", + statusCode: 200, + contentType: "text/html", + }, + { + name: "Should be returns status 200", + url: "/dir/img/fiber.png", + statusCode: 200, + contentType: "image/png", + }, + { + name: "Should be return status 200", + url: "/spatest/doesnotexist", + statusCode: 200, + contentType: "text/html", + }, + { + name: "PathPrefix should be applied", + url: "/prefix/fiber.png", + statusCode: 200, + contentType: "image/png", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tt.url, nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, tt.statusCode, resp.StatusCode) + + if tt.contentType != "" { + ct := resp.Header.Get("Content-Type") + utils.AssertEqual(t, tt.contentType, ct) + } + }) + } +} + +// go test -run Test_FileSystem_Next +func Test_FileSystem_Next(t *testing.T) { + t.Parallel() + app := fiber.New() + app.Use(New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + Next: func(_ *fiber.Ctx) bool { + return true + }, + })) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode) +} + +func Test_FileSystem_NonGetAndHead(t *testing.T) { + t.Parallel() + app := fiber.New() + + app.Use("/test", New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + })) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/test", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 404, resp.StatusCode) +} + +func Test_FileSystem_Head(t *testing.T) { + t.Parallel() + app := fiber.New() + + app.Use("/test", New(Config{ + Root: http.Dir("../../.github/testdata/fs"), + })) + + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/test", nil) + utils.AssertEqual(t, nil, err) + resp, err := app.Test(req) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) +} + +func Test_FileSystem_NoRoot(t *testing.T) { + t.Parallel() + defer func() { + utils.AssertEqual(t, "filesystem: Root cannot be nil", recover()) + }() + + app := fiber.New() + app.Use(New()) + _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + utils.AssertEqual(t, nil, err) +} + +func Test_FileSystem_UsingParam(t *testing.T) { + t.Parallel() + app := fiber.New() + + app.Use("/:path", func(c *fiber.Ctx) error { + return SendFile(c, http.Dir("../../.github/testdata/fs"), c.Params("path")+".html") + }) + + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/index", nil) + utils.AssertEqual(t, nil, err) + resp, err := app.Test(req) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) +} + +func Test_FileSystem_UsingParam_NonFile(t *testing.T) { + t.Parallel() + app := fiber.New() + + app.Use("/:path", func(c *fiber.Ctx) error { + return SendFile(c, http.Dir("../../.github/testdata/fs"), c.Params("path")+".html") + }) + + req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/template", nil) + utils.AssertEqual(t, nil, err) + resp, err := app.Test(req) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 404, resp.StatusCode) +} + +func Test_FileSystem_UsingContentTypeCharset(t *testing.T) { + t.Parallel() + app := fiber.New() + app.Use(New(Config{ + Root: http.Dir("../../.github/testdata/fs/index.html"), + ContentTypeCharset: "UTF-8", + })) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) + utils.AssertEqual(t, "text/html; charset=UTF-8", resp.Header.Get("Content-Type")) +} diff --git a/helpers/middleware/filesystem/utils.go b/helpers/middleware/filesystem/utils.go new file mode 100644 index 0000000..4e96db6 --- /dev/null +++ b/helpers/middleware/filesystem/utils.go @@ -0,0 +1,66 @@ +package filesystem + +import ( + "fmt" + "html" + "net/http" + "os" + "path" + "sort" + "strings" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/utils" +) + +func getFileExtension(p string) string { + n := strings.LastIndexByte(p, '.') + if n < 0 { + return "" + } + return p[n:] +} + +func dirList(c *fiber.Ctx, f http.File) error { + fileinfos, err := f.Readdir(-1) + if err != nil { + return fmt.Errorf("failed to read dir: %w", err) + } + + fm := make(map[string]os.FileInfo, len(fileinfos)) + filenames := make([]string, 0, len(fileinfos)) + for _, fi := range fileinfos { + name := fi.Name() + fm[name] = fi + filenames = append(filenames, name) + } + + basePathEscaped := html.EscapeString(c.Path()) + _, _ = fmt.Fprintf(c, "%s", basePathEscaped) + _, _ = fmt.Fprintf(c, "

%s

", basePathEscaped) + _, _ = fmt.Fprint(c, "") + + c.Type("html") + + return nil +} diff --git a/helpers/middleware/static.go b/helpers/middleware/static.go index 040be9e..080a4a8 100644 --- a/helpers/middleware/static.go +++ b/helpers/middleware/static.go @@ -4,15 +4,16 @@ import ( "io/fs" "net/http" + "github.com/Theodor-Springmann-Stiftung/lenz-web/helpers/middleware/filesystem" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/filesystem" ) -func StaticHandler(fs *fs.FS) fiber.Handler { +func StaticHandler(fs *fs.FS, dev bool) fiber.Handler { return filesystem.New(filesystem.Config{ Root: http.FS(*fs), Browse: false, Index: "index.html", + Dev: dev, MaxAge: 3600, }) } diff --git a/scratchpad.md b/scratchpad.md index 79455d6..568ba57 100644 --- a/scratchpad.md +++ b/scratchpad.md @@ -50,15 +50,3 @@ hand insertion subst -Korrekturversion der Lenz-Briefe online -Eine Korrekturversion der Briefe kann jetzt unter https://dev.lenz-briefe.de eingesehen werden. Die Seite aktualisiert sich bei einem push nach https://github.com/Theodor-Springmann-Stiftung/lenz-briefe automatisch, wenn die XML-Syntax gültig ist. -Ein paar Dinge zur Korrektur: -Grundsätzlich hatte ich schon Probleme mit dem Whitespace, sowohl veritikal, als auch horizontal. In allen XML-Dokumentformaten (wie in TEI) werden Block-Elemente und Inline-Elemente unterschieden. Unser einziges Block-Element ist und zzt. , dass bedeutet, dass vor und nach beiden Elementen automatisch ein Zeilenumbruch stattfindet (sie werden als "block" gesetzt). Fängt man eines von beiden dennoch mit an, gibt es halt einen Zeilenumbruch zu Anfang des Elements: -Bitte nach einem -Tag direkt mit der neuen Seite weiter machen (oder , wenn etwa ein Absatz folgt. Bitte auch keinen Zeilenumbruch nach , sonst gibt es ein unschönes Spatium am Zeilenanfang. -Grundsätzlich sind für mein Gefühl zu viele . Zb vor scheinen sie ein bisschen sinnlos, aber das muss man wohl im Einzelfall beurteilen. - und müssen nicht mit kommentiert werden, was wirklich oft geschieht. Evtl. lohnt es sich einfach alle -Elemente in der briefe.xml zu suchen, evtl. zu löschen und ggf. durch oder zu ersetzen. Das kann man ganz ohne Textgrundlage machen, einfach auf Basis dessen, was in der steht: -Villeicht müssen wir uns für einen anderen Platz ausdenken, als im laufenden Text; evtl am Seitenende in einer zweiten Spalte oder so? -Dass wir zwischen [added] und unterscheiden, haben wir ja besprochen ([added] sollte selten sein). -Grundsätzlich haben ein bisschen das Problem, dass sie sich nicht wirklich auf etwas beziehen, bzw. der Bezug oft unklar ist. Am liebsten würde mir so etwas wie und aus der Hamann-Ausgabe besser gefallen. Dort ist note durch den rigiden Zeilenfall nicht so sehr das Problem. - - diff --git a/server/functions.go b/server/functions.go index 7f1bc56..51d32a0 100644 --- a/server/functions.go +++ b/server/functions.go @@ -1,7 +1,15 @@ package server -import "github.com/gofiber/fiber/v2" +import ( + "log/slog" + "strings" + + "github.com/gofiber/fiber/v2" +) func CacheFunc(c *fiber.Ctx) bool { - return c.Query("noCache") == "true" || c.Response().StatusCode() != fiber.StatusOK + path := c.Path() + // INFO: for now, css and js files are excluded from caching; they get cached via ETag to enable reloading on style changes. + slog.Debug("CacheFunc:", "path", path) + return c.Query("noCache") == "true" || c.Response().StatusCode() != fiber.StatusOK || strings.HasPrefix(path, "/assets") } diff --git a/server/server.go b/server/server.go index 1ef0afc..39223f7 100644 --- a/server/server.go +++ b/server/server.go @@ -62,7 +62,7 @@ func New(engine *templating.Engine, storage fiber.Storage, debug bool) Server { server.Use(cache.New(cache.Config{ Next: CacheFunc, Expiration: CACHE_TIME, - CacheControl: true, + CacheControl: false, Storage: storage, KeyGenerator: KeyGenerator, }))