mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-06 11:02:01 +00:00
middleware: add static middleware (#3006)
* middleware: add static middleware * uncomment broken tests * introduce isfile config property to fix file issues * test * add io/fs support to static mw * add io/fs support to static mw * remove filesystem and app.Static * fix linter * apply review * support disablecache * support multi indexes * add an example for io/fs * update whats new & apply reviews * update * use fasthttp from master * Update .github/README.md Co-authored-by: RW <rene@gofiber.io> * update1 * apply reviews * update * update * update examples * add more examples --------- Co-authored-by: RW <rene@gofiber.io>
This commit is contained in:
parent
fca62c1853
commit
38fb8064c6
10
.github/README.md
vendored
10
.github/README.md
vendored
@ -203,15 +203,15 @@ func main() {
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Static("/", "./public")
|
||||
app.Get("/*", static.New("./public"))
|
||||
// => http://localhost:3000/js/script.js
|
||||
// => http://localhost:3000/css/style.css
|
||||
|
||||
app.Static("/prefix", "./public")
|
||||
app.Get("/prefix*", static.New("./public"))
|
||||
// => http://localhost:3000/prefix/js/script.js
|
||||
// => http://localhost:3000/prefix/css/style.css
|
||||
|
||||
app.Static("*", "./public/index.html")
|
||||
app.Get("*", static.New("./public/index.html"))
|
||||
// => http://localhost:3000/any/path/shows/index/html
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
@ -388,7 +388,7 @@ curl -H "Origin: http://example.com" --verbose http://localhost:3000
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Static("/", "./public")
|
||||
app.Get("/", static.New("./public"))
|
||||
|
||||
app.Get("/demo", func(c fiber.Ctx) error {
|
||||
return c.SendString("This is a demo!")
|
||||
@ -586,7 +586,6 @@ Here is a list of middleware that are included within the Fiber framework.
|
||||
| [etag](https://github.com/gofiber/fiber/tree/main/middleware/etag) | Allows for caches to be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. |
|
||||
| [expvar](https://github.com/gofiber/fiber/tree/main/middleware/expvar) | Serves via its HTTP server runtime exposed variants in the JSON format. |
|
||||
| [favicon](https://github.com/gofiber/fiber/tree/main/middleware/favicon) | Ignore favicon from logs or serve from memory if a file path is provided. |
|
||||
| [filesystem](https://github.com/gofiber/fiber/tree/main/middleware/filesystem) | FileSystem middleware for Fiber. |
|
||||
| [healthcheck](https://github.com/gofiber/fiber/tree/main/middleware/healthcheck) | Liveness and Readiness probes for Fiber. |
|
||||
| [helmet](https://github.com/gofiber/fiber/tree/main/middleware/helmet) | Helps secure your apps by setting various HTTP headers. |
|
||||
| [idempotency](https://github.com/gofiber/fiber/tree/main/middleware/idempotency) | Allows for fault-tolerant APIs where duplicate requests do not erroneously cause the same action performed multiple times on the server-side. |
|
||||
@ -601,6 +600,7 @@ Here is a list of middleware that are included within the Fiber framework.
|
||||
| [rewrite](https://github.com/gofiber/fiber/tree/main/middleware/rewrite) | Rewrites the URL path based on provided rules. It can be helpful for backward compatibility or just creating cleaner and more descriptive links. |
|
||||
| [session](https://github.com/gofiber/fiber/tree/main/middleware/session) | Session middleware. NOTE: This middleware uses our Storage package. |
|
||||
| [skip](https://github.com/gofiber/fiber/tree/main/middleware/skip) | Skip middleware that skips a wrapped handler if a predicate is true. |
|
||||
| [static](https://github.com/gofiber/fiber/tree/main/middleware/static) | Static middleware for Fiber that serves static files such as **images**, **CSS,** and **JavaScript**. |
|
||||
| [timeout](https://github.com/gofiber/fiber/tree/main/middleware/timeout) | Adds a max time for a request and forwards to ErrorHandler if it is exceeded. |
|
||||
|
||||
## 🧬 External Middleware
|
||||
|
53
app.go
53
app.go
@ -381,52 +381,6 @@ type Config struct {
|
||||
EnableSplittingOnParsers bool `json:"enable_splitting_on_parsers"`
|
||||
}
|
||||
|
||||
// Static defines configuration options when defining static assets.
|
||||
type Static struct {
|
||||
// When set to true, the server tries minimizing CPU usage by caching compressed files.
|
||||
// This works differently than the github.com/gofiber/compression middleware.
|
||||
// Optional. Default value false
|
||||
Compress bool `json:"compress"`
|
||||
|
||||
// When set to true, enables byte range requests.
|
||||
// Optional. Default value false
|
||||
ByteRange bool `json:"byte_range"`
|
||||
|
||||
// When set to true, enables directory browsing.
|
||||
// Optional. Default value false.
|
||||
Browse bool `json:"browse"`
|
||||
|
||||
// When set to true, enables direct download.
|
||||
// Optional. Default value false.
|
||||
Download bool `json:"download"`
|
||||
|
||||
// The name of the index file for serving a directory.
|
||||
// Optional. Default value "index.html".
|
||||
Index string `json:"index"`
|
||||
|
||||
// Expiration duration for inactive file handlers.
|
||||
// Use a negative time.Duration to disable it.
|
||||
//
|
||||
// Optional. Default value 10 * time.Second.
|
||||
CacheDuration time.Duration `json:"cache_duration"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// ModifyResponse defines a function that allows you to alter the response.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
ModifyResponse Handler
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c Ctx) bool
|
||||
}
|
||||
|
||||
// RouteMessage is some message need to be print when server starts
|
||||
type RouteMessage struct {
|
||||
name string
|
||||
@ -780,13 +734,6 @@ func (app *App) Add(methods []string, path string, handler Handler, middleware .
|
||||
return app
|
||||
}
|
||||
|
||||
// Static will create a file server serving static files
|
||||
func (app *App) Static(prefix, root string, config ...Static) Router {
|
||||
app.registerStatic(prefix, root, config...)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
// All will register the handler on all HTTP methods
|
||||
func (app *App) All(path string, handler Handler, middleware ...Handler) Router {
|
||||
return app.Add(app.config.RequestMethods, path, handler, middleware...)
|
||||
|
318
app_test.go
318
app_test.go
@ -901,314 +901,6 @@ func Test_App_ShutdownWithContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// go test -run Test_App_Static_Index_Default
|
||||
func Test_App_Static_Index_Default(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("/prefix", "./.github/workflows")
|
||||
app.Static("", "./.github/")
|
||||
app.Static("test", "", Static{Index: "index.html"})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/not-found", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Cannot GET /not-found", string(body))
|
||||
}
|
||||
|
||||
// go test -run Test_App_Static_Index
|
||||
func Test_App_Static_Direct(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("/", "./.github")
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/testdata/testRoutes.json", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMEApplicationJSON, resp.Header.Get("Content-Type"))
|
||||
require.Equal(t, "", resp.Header.Get(HeaderCacheControl), "CacheControl Control")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "test_routes")
|
||||
}
|
||||
|
||||
// go test -run Test_App_Static_MaxAge
|
||||
func Test_App_Static_MaxAge(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("/", "./.github", Static{MaxAge: 100})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get(HeaderContentType))
|
||||
require.Equal(t, "public, max-age=100", resp.Header.Get(HeaderCacheControl), "CacheControl Control")
|
||||
}
|
||||
|
||||
// go test -run Test_App_Static_Custom_CacheControl
|
||||
func Test_App_Static_Custom_CacheControl(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("/", "./.github", Static{ModifyResponse: func(c Ctx) error {
|
||||
if strings.Contains(c.GetRespHeader("Content-Type"), "text/html") {
|
||||
c.Response().Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
}
|
||||
return nil
|
||||
}})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, "no-cache, no-store, must-revalidate", resp.Header.Get(HeaderCacheControl), "CacheControl Control")
|
||||
|
||||
normalResp, normalErr := app.Test(httptest.NewRequest(MethodGet, "/config.yml", nil))
|
||||
require.NoError(t, normalErr, "app.Test(req)")
|
||||
require.Equal(t, "", normalResp.Header.Get(HeaderCacheControl), "CacheControl Control")
|
||||
}
|
||||
|
||||
// go test -run Test_App_Static_Download
|
||||
func Test_App_Static_Download(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("/fiber.png", "./.github/testdata/fs/img/fiber.png", Static{Download: true})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/fiber.png", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, "image/png", resp.Header.Get(HeaderContentType))
|
||||
require.Equal(t, `attachment`, resp.Header.Get(HeaderContentDisposition))
|
||||
}
|
||||
|
||||
// go test -run Test_App_Static_Group
|
||||
func Test_App_Static_Group(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
grp := app.Group("/v1", func(c Ctx) error {
|
||||
c.Set("Test-Header", "123")
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
grp.Static("/v2", "./.github/index.html")
|
||||
|
||||
req := httptest.NewRequest(MethodGet, "/v1/v2", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
require.Equal(t, "123", resp.Header.Get("Test-Header"))
|
||||
|
||||
grp = app.Group("/v2")
|
||||
grp.Static("/v3*", "./.github/index.html")
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/v2/v3/john/doe", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
}
|
||||
|
||||
func Test_App_Static_Wildcard(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("*", "./.github/index.html")
|
||||
|
||||
req := httptest.NewRequest(MethodGet, "/yesyes/john/doe", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Test file")
|
||||
}
|
||||
|
||||
func Test_App_Static_Prefix_Wildcard(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Static("/test/*", "./.github/index.html")
|
||||
|
||||
req := httptest.NewRequest(MethodGet, "/test/john/doe", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
app.Static("/my/nameisjohn*", "./.github/index.html")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/my/nameisjohn/no/its/not", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Test file")
|
||||
}
|
||||
|
||||
func Test_App_Static_Prefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Static("/john", "./.github")
|
||||
|
||||
req := httptest.NewRequest(MethodGet, "/john/index.html", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
app.Static("/prefix", "./.github/testdata")
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/prefix/index.html", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
app.Static("/single", "./.github/testdata/testRoutes.json")
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/single", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMEApplicationJSON, resp.Header.Get(HeaderContentType))
|
||||
}
|
||||
|
||||
func Test_App_Static_Trailing_Slash(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Static("/john", "./.github")
|
||||
|
||||
req := httptest.NewRequest(MethodGet, "/john/", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
app.Static("/john_without_index", "./.github/testdata/fs/css")
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/john_without_index/", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
app.Static("/john/", "./.github")
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/john/", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/john", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
app.Static("/john_without_index/", "./.github/testdata/fs/css")
|
||||
|
||||
req = httptest.NewRequest(MethodGet, "/john_without_index/", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
}
|
||||
|
||||
func Test_App_Static_Next(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
app.Static("/", ".github", Static{
|
||||
Next: func(c Ctx) bool {
|
||||
// If value of the header is any other from "skip"
|
||||
// c.Next() will be invoked
|
||||
return c.Get("X-Custom-Header") == "skip"
|
||||
},
|
||||
})
|
||||
app.Get("/", func(c Ctx) error {
|
||||
return c.SendString("You've skipped app.Static")
|
||||
})
|
||||
|
||||
t.Run("app.Static is skipped: invoking Get handler", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := httptest.NewRequest(MethodGet, "/", nil)
|
||||
req.Header.Set("X-Custom-Header", "skip")
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "You've skipped app.Static")
|
||||
})
|
||||
|
||||
t.Run("app.Static is not skipped: serving index.html", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := httptest.NewRequest(MethodGet, "/", nil)
|
||||
req.Header.Set("X-Custom-Header", "don't skip")
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
})
|
||||
}
|
||||
|
||||
// go test -run Test_App_Mixed_Routes_WithSameLen
|
||||
func Test_App_Mixed_Routes_WithSameLen(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -1220,7 +912,10 @@ func Test_App_Mixed_Routes_WithSameLen(t *testing.T) {
|
||||
return c.Next()
|
||||
})
|
||||
// routes with the same length
|
||||
app.Static("/tesbar", "./.github")
|
||||
app.Get("/tesbar", func(c Ctx) error {
|
||||
c.Type("html")
|
||||
return c.Send([]byte("TEST_BAR"))
|
||||
})
|
||||
app.Get("/foobar", func(c Ctx) error {
|
||||
c.Type("html")
|
||||
return c.Send([]byte("FOO_BAR"))
|
||||
@ -1246,12 +941,11 @@ func Test_App_Mixed_Routes_WithSameLen(t *testing.T) {
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||
require.Equal(t, "TestValue", resp.Header.Get("TestHeader"))
|
||||
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get(HeaderContentType))
|
||||
require.Equal(t, "text/html", resp.Header.Get(HeaderContentType))
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
require.True(t, strings.HasPrefix(string(body), "<!DOCTYPE html>"), "Response: "+string(body))
|
||||
require.Contains(t, string(body), "TEST_BAR")
|
||||
}
|
||||
|
||||
func Test_App_Group_Invalid(t *testing.T) {
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
MIMETextHTML = "text/html"
|
||||
MIMETextPlain = "text/plain"
|
||||
MIMETextJavaScript = "text/javascript"
|
||||
MIMETextCSS = "text/css"
|
||||
MIMEApplicationXML = "application/xml"
|
||||
MIMEApplicationJSON = "application/json"
|
||||
// Deprecated: use MIMETextJavaScript instead
|
||||
@ -32,6 +33,7 @@ const (
|
||||
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
||||
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
|
||||
MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
|
||||
MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8"
|
||||
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
|
||||
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
||||
// Deprecated: use MIMETextJavaScriptCharsetUTF8 instead
|
||||
|
@ -11,70 +11,6 @@ import Reference from '@site/src/components/reference';
|
||||
|
||||
import RoutingHandler from './../partials/routing/handler.md';
|
||||
|
||||
### Static
|
||||
|
||||
Use the **Static** method to serve static files such as **images**, **CSS,** and **JavaScript**.
|
||||
|
||||
:::info
|
||||
By default, **Static** will serve `index.html` files in response to a request on a directory.
|
||||
:::
|
||||
|
||||
```go title="Signature"
|
||||
func (app *App) Static(prefix, root string, config ...Static) Router
|
||||
```
|
||||
|
||||
Use the following code to serve files in a directory named `./public`
|
||||
|
||||
```go title="Examples"
|
||||
// Serve files from multiple directories
|
||||
app.Static("/", "./public")
|
||||
|
||||
// => http://localhost:3000/hello.html
|
||||
// => http://localhost:3000/js/jquery.js
|
||||
// => http://localhost:3000/css/style.css
|
||||
|
||||
// Serve files from "./files" directory:
|
||||
app.Static("/", "./files")
|
||||
```
|
||||
|
||||
You can use any virtual path prefix \(_where the path does not actually exist in the file system_\) for files that are served by the **Static** method, specify a prefix path for the static directory, as shown below:
|
||||
|
||||
```go title="Examples"
|
||||
app.Static("/static", "./public")
|
||||
|
||||
// => http://localhost:3000/static/hello.html
|
||||
// => http://localhost:3000/static/js/jquery.js
|
||||
// => http://localhost:3000/static/css/style.css
|
||||
```
|
||||
|
||||
#### Config
|
||||
|
||||
If you want to have a little bit more control regarding the settings for serving static files. You could use the `fiber.Static` struct to enable specific settings.
|
||||
|
||||
| Property | Type | Description | Default |
|
||||
|------------------------------------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
|
||||
| <Reference id="compress">Compress</Reference> | `bool` | When set to true, the server tries minimizing CPU usage by caching compressed files. This works differently than the [compress](../middleware/compress.md) middleware. | false |
|
||||
| <Reference id="byte_range">ByteRange</Reference> | `bool` | When set to true, enables byte range requests. | false |
|
||||
| <Reference id="browse">Browse</Reference> | `bool` | When set to true, enables directory browsing. | false |
|
||||
| <Reference id="download">Download</Reference> | `bool` | When set to true, enables direct download. | false |
|
||||
| <Reference id="index">Index</Reference> | `string` | The name of the index file for serving a directory. | "index.html" |
|
||||
| <Reference id="cache_duration">CacheDuration</Reference> | `time.Duration` | Expiration duration for inactive file handlers. Use a negative `time.Duration` to disable it. | 10 * time.Second |
|
||||
| <Reference id="max_age">MaxAge</Reference> | `int` | The value for the `Cache-Control` HTTP-header that is set on the file response. MaxAge is defined in seconds. | 0 |
|
||||
| <Reference id="modify_response">ModifyResponse</Reference> | `Handler` | ModifyResponse defines a function that allows you to alter the response. | nil |
|
||||
| <Reference id="next">Next</Reference> | `func(c Ctx) bool` | Next defines a function to skip this middleware when returned true. | nil |
|
||||
|
||||
```go title="Example"
|
||||
// Custom config
|
||||
app.Static("/", "./public", fiber.Static{
|
||||
Compress: true,
|
||||
ByteRange: true,
|
||||
Browse: true,
|
||||
Index: "john.html",
|
||||
CacheDuration: 10 * time.Second,
|
||||
MaxAge: 3600,
|
||||
})
|
||||
```
|
||||
|
||||
### Route Handlers
|
||||
|
||||
<RoutingHandler />
|
||||
@ -181,8 +117,6 @@ type Register interface {
|
||||
|
||||
Add(methods []string, handler Handler, middleware ...Handler) Register
|
||||
|
||||
Static(root string, config ...Static) Register
|
||||
|
||||
Route(path string) Register
|
||||
}
|
||||
```
|
||||
|
@ -26,24 +26,27 @@ const (
|
||||
|
||||
```go
|
||||
const (
|
||||
MIMETextXML = "text/xml"
|
||||
MIMETextHTML = "text/html"
|
||||
MIMETextPlain = "text/plain"
|
||||
MIMEApplicationXML = "application/xml"
|
||||
MIMEApplicationJSON = "application/json"
|
||||
MIMETextXML = "text/xml"
|
||||
MIMETextHTML = "text/html"
|
||||
MIMETextPlain = "text/plain"
|
||||
MIMETextJavaScript = "text/javascript"
|
||||
MIMETextCSS = "text/css"
|
||||
MIMEApplicationXML = "application/xml"
|
||||
MIMEApplicationJSON = "application/json"
|
||||
MIMEApplicationJavaScript = "application/javascript"
|
||||
MIMEApplicationForm = "application/x-www-form-urlencoded"
|
||||
MIMEOctetStream = "application/octet-stream"
|
||||
MIMEMultipartForm = "multipart/form-data"
|
||||
|
||||
MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
|
||||
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
||||
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
|
||||
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
|
||||
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
||||
MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
|
||||
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
||||
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
|
||||
MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
|
||||
MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8"
|
||||
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
|
||||
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
||||
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
|
||||
)
|
||||
```
|
||||
)```
|
||||
|
||||
### HTTP status codes were copied from net/http.
|
||||
|
||||
|
@ -164,19 +164,15 @@ app.Get("/api/*", func(c fiber.Ctx) error {
|
||||
### Static files
|
||||
|
||||
To serve static files such as **images**, **CSS**, and **JavaScript** files, replace your function handler with a file or directory string.
|
||||
|
||||
You can check out [static middleware](./middleware/static.md) for more information.
|
||||
Function signature:
|
||||
|
||||
```go
|
||||
app.Static(prefix, root string, config ...Static)
|
||||
```
|
||||
|
||||
Use the following code to serve files in a directory named `./public`:
|
||||
|
||||
```go
|
||||
app := fiber.New()
|
||||
|
||||
app.Static("/", "./public")
|
||||
app.Get("/*", static.New("./public"))
|
||||
|
||||
app.Listen(":3000")
|
||||
```
|
||||
|
@ -1,300 +0,0 @@
|
||||
---
|
||||
id: filesystem
|
||||
---
|
||||
|
||||
# FileSystem
|
||||
|
||||
Filesystem middleware for [Fiber](https://github.com/gofiber/fiber) that enables you to serve files from a directory.
|
||||
|
||||
:::caution
|
||||
**`:params` & `:optionals?` within the prefix path are not supported!**
|
||||
|
||||
**To handle paths with spaces (or other url encoded values) make sure to set `fiber.Config{ UnescapePath: true }`**
|
||||
:::
|
||||
|
||||
## Signatures
|
||||
|
||||
```go
|
||||
func New(config Config) fiber.Handler
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Import the middleware package that is part of the Fiber web framework
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
)
|
||||
```
|
||||
|
||||
After you initiate your Fiber app, you can use the following possibilities:
|
||||
|
||||
```go
|
||||
// Provide a minimal config
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.Dir("./assets"),
|
||||
}))
|
||||
|
||||
// Or extend your config for customization
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.Dir("./assets"),
|
||||
Browse: true,
|
||||
Index: "index.html",
|
||||
NotFoundFile: "404.html",
|
||||
MaxAge: 3600,
|
||||
}))
|
||||
```
|
||||
|
||||
|
||||
> If your environment (Go 1.16+) supports it, we recommend using Go Embed instead of the other solutions listed as this one is native to Go and the easiest to use.
|
||||
|
||||
## embed
|
||||
|
||||
[Embed](https://golang.org/pkg/embed/) is the native method to embed files in a Golang excecutable. Introduced in Go 1.16.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
)
|
||||
|
||||
// Embed a single file
|
||||
//go:embed index.html
|
||||
var f embed.FS
|
||||
|
||||
// Embed a directory
|
||||
//go:embed static/*
|
||||
var embedDirStatic embed.FS
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(f),
|
||||
}))
|
||||
|
||||
// Access file "image.png" under `static/` directory via URL: `http://<server>/static/image.png`.
|
||||
// Without `PathPrefix`, you have to access it via URL:
|
||||
// `http://<server>/static/static/image.png`.
|
||||
app.Use("/static", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(embedDirStatic),
|
||||
PathPrefix: "static",
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
## pkger
|
||||
|
||||
[https://github.com/markbates/pkger](https://github.com/markbates/pkger)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
|
||||
"github.com/markbates/pkger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/assets", filesystem.New(filesystem.Config{
|
||||
Root: pkger.Dir("/assets"),
|
||||
}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
## packr
|
||||
|
||||
[https://github.com/gobuffalo/packr](https://github.com/gobuffalo/packr)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/assets", filesystem.New(filesystem.Config{
|
||||
Root: packr.New("Assets Box", "/assets"),
|
||||
}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
## go.rice
|
||||
|
||||
[https://github.com/GeertJohan/go.rice](https://github.com/GeertJohan/go.rice)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
|
||||
"github.com/GeertJohan/go.rice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/assets", filesystem.New(filesystem.Config{
|
||||
Root: rice.MustFindBox("assets").HTTPBox(),
|
||||
}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
## fileb0x
|
||||
|
||||
[https://github.com/UnnoTed/fileb0x](https://github.com/UnnoTed/fileb0x)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
|
||||
"<Your go module>/myEmbeddedFiles"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/assets", filesystem.New(filesystem.Config{
|
||||
Root: myEmbeddedFiles.HTTP,
|
||||
}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
## statik
|
||||
|
||||
[https://github.com/rakyll/statik](https://github.com/rakyll/statik)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
|
||||
// Use blank to invoke init function and register data to statik
|
||||
_ "<Your go module>/statik"
|
||||
"github.com/rakyll/statik/fs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: statikFS,
|
||||
}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
| Property | Type | Description | Default |
|
||||
|:-------------------|:------------------------|:------------------------------------------------------------------------------------------------------------|:-------------|
|
||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
||||
| Root | `http.FileSystem` | Root is a FileSystem that provides access to a collection of files and directories. | `nil` |
|
||||
| PathPrefix | `string` | PathPrefix defines a prefix to be added to a filepath when reading a file from the FileSystem. | "" |
|
||||
| Browse | `bool` | Enable directory browsing. | `false` |
|
||||
| Index | `string` | Index file for serving a directory. | "index.html" |
|
||||
| MaxAge | `int` | The value for the Cache-Control HTTP-header that is set on the file response. MaxAge is defined in seconds. | 0 |
|
||||
| NotFoundFile | `string` | File to return if the path is not found. Useful for SPA's. | "" |
|
||||
| ContentTypeCharset | `string` | The value for the Content-Type HTTP-header that is set on the file response. | "" |
|
||||
|
||||
## Default Config
|
||||
|
||||
```go
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Root: nil,
|
||||
PathPrefix: "",
|
||||
Browse: false,
|
||||
Index: "/index.html",
|
||||
MaxAge: 0,
|
||||
ContentTypeCharset: "",
|
||||
}
|
||||
```
|
||||
|
||||
## Utils
|
||||
|
||||
### SendFile
|
||||
|
||||
Serves a file from an [HTTP file system](https://pkg.go.dev/net/http#FileSystem) at the specified path.
|
||||
|
||||
```go title="Signature" title="Signature"
|
||||
func SendFile(c fiber.Ctx, filesystem http.FileSystem, path string) error
|
||||
```
|
||||
Import the middleware package that is part of the Fiber web framework
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
)
|
||||
```
|
||||
|
||||
```go title="Example"
|
||||
// Define a route to serve a specific file
|
||||
app.Get("/download", func(c fiber.Ctx) error {
|
||||
// Serve the file using SendFile function
|
||||
err := filesystem.SendFile(c, http.Dir("your/filesystem/root"), "path/to/your/file.txt")
|
||||
if err != nil {
|
||||
// Handle the error, e.g., return a 404 Not Found response
|
||||
return c.Status(fiber.StatusNotFound).SendString("File not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
```go title="Example"
|
||||
// Serve static files from the "build" directory using Fiber's built-in middleware.
|
||||
app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(f), // Specify the root directory for static files.
|
||||
PathPrefix: "build", // Define the path prefix where static files are served.
|
||||
}))
|
||||
|
||||
// For all other routes (wildcard "*"), serve the "index.html" file from the "build" directory.
|
||||
app.Use("*", func(ctx fiber.Ctx) error {
|
||||
return filesystem.SendFile(ctx, http.FS(f), "build/index.html")
|
||||
})
|
||||
```
|
172
docs/middleware/static.md
Normal file
172
docs/middleware/static.md
Normal file
@ -0,0 +1,172 @@
|
||||
---
|
||||
id: static
|
||||
---
|
||||
|
||||
# Static
|
||||
|
||||
Static middleware for Fiber that serves static files such as **images**, **CSS,** and **JavaScript**.
|
||||
|
||||
:::info
|
||||
By default, **Static** will serve `index.html` files in response to a request on a directory. You can change it from [Config](#config)`
|
||||
:::
|
||||
|
||||
## Signatures
|
||||
|
||||
```go
|
||||
func New(root string, cfg ...Config) fiber.Handler
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Import the middleware package that is part of the [Fiber](https://github.com/gofiber/fiber) web framework
|
||||
```go
|
||||
import(
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/static"
|
||||
)
|
||||
```
|
||||
|
||||
### Serving files from a directory
|
||||
|
||||
```go
|
||||
app.Get("/*", static.New("./public"))
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Test</summary>
|
||||
|
||||
```sh
|
||||
curl http://localhost:3000/hello.html
|
||||
curl http://localhost:3000/css/style.css
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Serving files from a directory with Use
|
||||
|
||||
```go
|
||||
app.Use("/", static.New("./public"))
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Test</summary>
|
||||
|
||||
```sh
|
||||
curl http://localhost:3000/hello.html
|
||||
curl http://localhost:3000/css/style.css
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Serving a file
|
||||
|
||||
```go
|
||||
app.Use("/static", static.New("./public/hello.html"))
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Test</summary>
|
||||
|
||||
```sh
|
||||
curl http://localhost:3000/static # will show hello.html
|
||||
curl http://localhost:3000/static/john/doee # will show hello.html
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Serving files using os.DirFS
|
||||
|
||||
```go
|
||||
app.Get("/files*", static.New("", static.Config{
|
||||
FS: os.DirFS("files"),
|
||||
Browse: true,
|
||||
}))
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Test</summary>
|
||||
|
||||
```sh
|
||||
curl http://localhost:3000/files/css/style.css
|
||||
curl http://localhost:3000/files/index.html
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Serving files using embed.FS
|
||||
|
||||
```go
|
||||
//go:embed path/to/files
|
||||
var myfiles embed.FS
|
||||
|
||||
app.Get("/files*", static.New("", static.Config{
|
||||
FS: myfiles,
|
||||
Browse: true,
|
||||
}))
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Test</summary>
|
||||
|
||||
```sh
|
||||
curl http://localhost:3000/files/css/style.css
|
||||
curl http://localhost:3000/files/index.html
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### SPA (Single Page Application)
|
||||
|
||||
```go
|
||||
app.Use("/web", static.New("", static.Config{
|
||||
FS: os.DirFS("dist"),
|
||||
}))
|
||||
|
||||
app.Get("/web*", func(c fiber.Ctx) error {
|
||||
return c.SendFile("dist/index.html")
|
||||
})
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Test</summary>
|
||||
|
||||
```sh
|
||||
curl http://localhost:3000/web/css/style.css
|
||||
curl http://localhost:3000/web/index.html
|
||||
curl http://localhost:3000/web
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
:::caution
|
||||
To define static routes using `Get`, append the wildcard (`*`) operator at the end of the route.
|
||||
:::
|
||||
|
||||
## Config
|
||||
|
||||
| Property | Type | Description | Default |
|
||||
|:-----------|:------------------------|:---------------------------------------------------------------------------------------------------------------------------|:-----------------------|
|
||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
||||
| FS | `fs.FS` | FS is the file system to serve the static files from.<br /><br />You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc. | `nil` |
|
||||
| Compress | `bool` | When set to true, the server tries minimizing CPU usage by caching compressed files.<br /><br />This works differently than the github.com/gofiber/compression middleware. | `false` |
|
||||
| ByteRange | `bool` | When set to true, enables byte range requests. | `false` |
|
||||
| Browse | `bool` | When set to true, enables directory browsing. | `false` |
|
||||
| Download | `bool` | When set to true, enables direct download. | `false` |
|
||||
| IndexNames | `[]string` | The names of the index files for serving a directory. | `[]string{"index.html"}` |
|
||||
| CacheDuration | `string` | Expiration duration for inactive file handlers.<br /><br />Use a negative time.Duration to disable it. | `10 * time.Second` |
|
||||
| MaxAge | `int` | The value for the Cache-Control HTTP-header that is set on the file response. MaxAge is defined in seconds. | `0` |
|
||||
| ModifyResponse | `fiber.Handler` | ModifyResponse defines a function that allows you to alter the response. | `nil` |
|
||||
| NotFoundHandler | `fiber.Handler` | NotFoundHandler defines a function to handle when the path is not found. | `nil` |
|
||||
|
||||
:::info
|
||||
You can set `CacheDuration` config property to `-1` to disable caching.
|
||||
:::
|
||||
|
||||
## Default Config
|
||||
|
||||
```go
|
||||
var ConfigDefault = Config{
|
||||
Index: []string{"index.html"},
|
||||
CacheDuration: 10 * time.Second,
|
||||
}
|
||||
```
|
@ -47,6 +47,7 @@ DRAFT section
|
||||
We have made several changes to the Fiber app, including:
|
||||
|
||||
* Listen -> unified with config
|
||||
* Static -> has been removed and moved to [static middleware](./middleware/static.md)
|
||||
* app.Config properties moved to listen config
|
||||
* DisableStartupMessage
|
||||
* EnablePrefork -> previously Prefork
|
||||
@ -270,9 +271,8 @@ DRAFT section
|
||||
|
||||
### Filesystem
|
||||
|
||||
:::caution
|
||||
DRAFT section
|
||||
:::
|
||||
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
||||
Now, static middleware can do everything that filesystem middleware and static do. You can check out [static middleware](./middleware/static.md) or [migration guide](#-migration-guide) to see what has been changed.
|
||||
|
||||
### Monitor
|
||||
|
||||
@ -295,6 +295,34 @@ Monitor middleware is now in Contrib package.
|
||||
|
||||
### 🚀 App
|
||||
|
||||
#### Static
|
||||
|
||||
Since we've removed `app.Static()`, you need to move methods to static middleware like the example below:
|
||||
|
||||
```go
|
||||
// Before
|
||||
app.Static("/", "./public")
|
||||
app.Static("/prefix", "./public")
|
||||
app.Static("/prefix", "./public", Static{
|
||||
Index: "index.htm",
|
||||
})
|
||||
app.Static("*", "./public/index.html")
|
||||
```
|
||||
|
||||
```go
|
||||
// After
|
||||
app.Get("/*", static.New("./public"))
|
||||
app.Get("/prefix*", static.New("./public"))
|
||||
app.Get("/prefix*", static.New("./public", static.Config{
|
||||
IndexNames: []string{"index.htm", "index.html"},
|
||||
}))
|
||||
app.Get("*", static.New("./public/index.html"))
|
||||
```
|
||||
|
||||
:::caution
|
||||
You have to put `*` to the end of the route if you don't define static route with `app.Use`.
|
||||
:::
|
||||
|
||||
### 🗺 Router
|
||||
|
||||
### 🧠 Context
|
||||
@ -328,4 +356,35 @@ app.Use(cors.New(cors.Config{
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
}))
|
||||
```
|
||||
...
|
||||
|
||||
#### Filesystem
|
||||
|
||||
You need to move filesystem middleware to static middleware due to it has been removed from the core.
|
||||
|
||||
```go
|
||||
// Before
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.Dir("./assets"),
|
||||
}))
|
||||
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.Dir("./assets"),
|
||||
Browse: true,
|
||||
Index: "index.html",
|
||||
MaxAge: 3600,
|
||||
}))
|
||||
```
|
||||
|
||||
```go
|
||||
// After
|
||||
app.Use(static.New("", static.Config{
|
||||
FS: os.DirFS("./assets"),
|
||||
}))
|
||||
|
||||
app.Use(static.New("", static.Config{
|
||||
FS: os.DirFS("./assets"),
|
||||
Browse: true,
|
||||
IndexNames: []string{"index.html"},
|
||||
MaxAge: 3600,
|
||||
}))
|
||||
```
|
10
group.go
10
group.go
@ -170,16 +170,6 @@ func (grp *Group) Add(methods []string, path string, handler Handler, middleware
|
||||
return grp
|
||||
}
|
||||
|
||||
// Static will create a file server serving static files
|
||||
func (grp *Group) Static(prefix, root string, config ...Static) Router {
|
||||
grp.app.registerStatic(getGroupPath(grp.Prefix, prefix), root, config...)
|
||||
if !grp.anyRouteDefined {
|
||||
grp.anyRouteDefined = true
|
||||
}
|
||||
|
||||
return grp
|
||||
}
|
||||
|
||||
// All will register the handler on all HTTP methods
|
||||
func (grp *Group) All(path string, handler Handler, middleware ...Handler) Router {
|
||||
_ = grp.Add(grp.app.config.RequestMethods, path, handler, middleware...)
|
||||
|
@ -1,323 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// 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 fs.FS `json:"-"`
|
||||
|
||||
// PathPrefix defines a prefix to be added to a filepath when
|
||||
// reading a file from the FileSystem.
|
||||
//
|
||||
// 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"`
|
||||
|
||||
// When set to true, enables direct download for files.
|
||||
//
|
||||
// Optional. Default: false.
|
||||
Download bool `json:"download"`
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Root: nil,
|
||||
PathPrefix: ".",
|
||||
Browse: false,
|
||||
Index: "/index.html",
|
||||
MaxAge: 0,
|
||||
ContentTypeCharset: "",
|
||||
}
|
||||
|
||||
// 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 cfg.PathPrefix == "" {
|
||||
cfg.PathPrefix = ConfigDefault.PathPrefix
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
// PathPrefix configurations for io/fs compatibility.
|
||||
if cfg.PathPrefix != "." && !strings.HasPrefix(cfg.PathPrefix, "/") {
|
||||
cfg.PathPrefix = "./" + cfg.PathPrefix
|
||||
}
|
||||
|
||||
if cfg.NotFoundFile != "" {
|
||||
cfg.NotFoundFile = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+cfg.NotFoundFile))
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
var prefix string
|
||||
cacheControlStr := "public, max-age=" + strconv.Itoa(cfg.MaxAge)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var (
|
||||
file fs.File
|
||||
stat os.FileInfo
|
||||
)
|
||||
|
||||
// Add PathPrefix
|
||||
if cfg.PathPrefix != "" {
|
||||
// PathPrefix already has a "/" prefix
|
||||
path = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+path))
|
||||
}
|
||||
|
||||
if len(path) > 1 {
|
||||
path = strings.TrimRight(path, "/")
|
||||
}
|
||||
|
||||
file, err := openFile(cfg.Root, path)
|
||||
|
||||
if err != nil && errors.Is(err, fs.ErrNotExist) && cfg.NotFoundFile != "" {
|
||||
file, err = openFile(cfg.Root, 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 := strings.TrimRight(path, "/") + cfg.Index
|
||||
indexPath = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+indexPath))
|
||||
|
||||
index, err := openFile(cfg.Root, 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))
|
||||
}
|
||||
|
||||
// Sets the response Content-Disposition header to attachment if the Download option is true and if it's a file
|
||||
if cfg.Download && !stat.IsDir() {
|
||||
c.Attachment()
|
||||
}
|
||||
|
||||
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 fs.FS filesystem 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 fs.FS, path string) error {
|
||||
var (
|
||||
file fs.File
|
||||
stat os.FileInfo
|
||||
)
|
||||
|
||||
path = filepath.Join(".", filepath.Clean("/"+path))
|
||||
|
||||
file, err := openFile(filesystem, 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 := strings.TrimRight(path, "/") + ConfigDefault.Index
|
||||
index, err := openFile(filesystem, 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
|
||||
}
|
@ -1,252 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// go test -run Test_FileSystem
|
||||
func Test_FileSystem(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/test", New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
}))
|
||||
|
||||
app.Use("/dir", New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
|
||||
app.Use("/spatest", New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Index: "index.html",
|
||||
NotFoundFile: "index.html",
|
||||
}))
|
||||
|
||||
app.Use("/prefix", New(Config{
|
||||
Root: os.DirFS("../../.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))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.statusCode, resp.StatusCode)
|
||||
|
||||
if tt.contentType != "" {
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
require.Equal(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: os.DirFS("../../.github/testdata/fs"),
|
||||
Next: func(_ fiber.Ctx) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
// go test -run Test_FileSystem_Download
|
||||
func Test_FileSystem_Download(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Download: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/img/fiber.png", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, "image/png", resp.Header.Get(fiber.HeaderContentType))
|
||||
require.Equal(t, "attachment", resp.Header.Get(fiber.HeaderContentDisposition))
|
||||
}
|
||||
|
||||
func Test_FileSystem_NonGetAndHead(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/test", New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/test", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 404, resp.StatusCode)
|
||||
}
|
||||
|
||||
func Test_FileSystem_Head(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/test", New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
}))
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/test", nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func Test_FileSystem_NoRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer func() {
|
||||
require.Equal(t, "filesystem: Root cannot be nil", recover())
|
||||
}()
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New())
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_FileSystem_UsingParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/:path", func(c fiber.Ctx) error {
|
||||
return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html")
|
||||
})
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/index", nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(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, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html")
|
||||
})
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), fiber.MethodHead, "/template", nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 404, resp.StatusCode)
|
||||
}
|
||||
|
||||
func Test_FileSystem_UsingContentTypeCharset(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Index: "index.html",
|
||||
ContentTypeCharset: "UTF-8",
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
require.Equal(t, "text/html; charset=UTF-8", resp.Header.Get("Content-Type"))
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// ErrDirListingNotSupported is returned from the filesystem middleware handler if
|
||||
// the given fs.FS does not support directory listing. This is uncommon and may
|
||||
// indicate an issue with the FS implementation.
|
||||
var ErrDirListingNotSupported = errors.New("failed to type-assert to fs.ReadDirFile")
|
||||
|
||||
func getFileExtension(p string) string {
|
||||
n := strings.LastIndexByte(p, '.')
|
||||
if n < 0 {
|
||||
return ""
|
||||
}
|
||||
return p[n:]
|
||||
}
|
||||
|
||||
func dirList(c fiber.Ctx, f fs.File) error {
|
||||
ff, ok := f.(fs.ReadDirFile)
|
||||
if !ok {
|
||||
return ErrDirListingNotSupported
|
||||
}
|
||||
fileinfos, err := ff.ReadDir(-1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read dir: %w", err)
|
||||
}
|
||||
|
||||
fm := make(map[string]fs.FileInfo, len(fileinfos))
|
||||
filenames := make([]string, 0, len(fileinfos))
|
||||
for _, fi := range fileinfos {
|
||||
name := fi.Name()
|
||||
info, err := fi.Info()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
fm[name] = info
|
||||
filenames = append(filenames, name)
|
||||
}
|
||||
|
||||
basePathEscaped := html.EscapeString(c.Path())
|
||||
_, _ = fmt.Fprintf(c, "<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>", basePathEscaped)
|
||||
_, _ = fmt.Fprintf(c, "<h1>%s</h1>", basePathEscaped)
|
||||
_, _ = fmt.Fprint(c, "<ul>")
|
||||
|
||||
if len(basePathEscaped) > 1 {
|
||||
parentPathEscaped := html.EscapeString(strings.TrimRight(c.Path(), "/") + "/..")
|
||||
_, _ = fmt.Fprintf(c, `<li><a href="%s" class="dir">..</a></li>`, parentPathEscaped)
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
for _, name := range filenames {
|
||||
pathEscaped := html.EscapeString(path.Join(c.Path() + "/" + name))
|
||||
fi := fm[name]
|
||||
auxStr := "dir"
|
||||
className := "dir"
|
||||
if !fi.IsDir() {
|
||||
auxStr = fmt.Sprintf("file, %d bytes", fi.Size())
|
||||
className = "file"
|
||||
}
|
||||
_, _ = fmt.Fprintf(c, `<li><a href="%s" class="%s">%s</a>, %s, last modified %s</li>`,
|
||||
pathEscaped, className, html.EscapeString(name), auxStr, fi.ModTime())
|
||||
}
|
||||
_, _ = fmt.Fprint(c, "</ul></body></html>")
|
||||
|
||||
c.Type("html")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openFile(filesystem fs.FS, name string) (fs.File, error) {
|
||||
name = filepath.ToSlash(name)
|
||||
|
||||
file, err := filesystem.Open(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
98
middleware/static/config.go
Normal file
98
middleware/static/config.go
Normal file
@ -0,0 +1,98 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// FS is the file system to serve the static files from.
|
||||
// You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
FS fs.FS
|
||||
|
||||
// When set to true, the server tries minimizing CPU usage by caching compressed files.
|
||||
// This works differently than the github.com/gofiber/compression middleware.
|
||||
//
|
||||
// Optional. Default: false
|
||||
Compress bool `json:"compress"`
|
||||
|
||||
// When set to true, enables byte range requests.
|
||||
//
|
||||
// Optional. Default: false
|
||||
ByteRange bool `json:"byte_range"`
|
||||
|
||||
// When set to true, enables directory browsing.
|
||||
//
|
||||
// Optional. Default: false.
|
||||
Browse bool `json:"browse"`
|
||||
|
||||
// When set to true, enables direct download.
|
||||
//
|
||||
// Optional. Default: false.
|
||||
Download bool `json:"download"`
|
||||
|
||||
// The names of the index files for serving a directory.
|
||||
//
|
||||
// Optional. Default: []string{"index.html"}.
|
||||
IndexNames []string `json:"index"`
|
||||
|
||||
// Expiration duration for inactive file handlers.
|
||||
// Use a negative time.Duration to disable it.
|
||||
//
|
||||
// Optional. Default: 10 * time.Second.
|
||||
CacheDuration time.Duration `json:"cache_duration"`
|
||||
|
||||
// The value for the Cache-Control HTTP-header
|
||||
// that is set on the file response. MaxAge is defined in seconds.
|
||||
//
|
||||
// Optional. Default: 0.
|
||||
MaxAge int `json:"max_age"`
|
||||
|
||||
// ModifyResponse defines a function that allows you to alter the response.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
ModifyResponse fiber.Handler
|
||||
|
||||
// NotFoundHandler defines a function to handle when the path is not found.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
NotFoundHandler fiber.Handler
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
IndexNames: []string{"index.html"},
|
||||
CacheDuration: 10 * time.Second,
|
||||
}
|
||||
|
||||
// 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
|
||||
if cfg.IndexNames == nil || len(cfg.IndexNames) == 0 {
|
||||
cfg.IndexNames = ConfigDefault.IndexNames
|
||||
}
|
||||
|
||||
if cfg.CacheDuration == 0 {
|
||||
cfg.CacheDuration = ConfigDefault.CacheDuration
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
175
middleware/static/static.go
Normal file
175
middleware/static/static.go
Normal file
@ -0,0 +1,175 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/utils/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// New creates a new middleware handler.
|
||||
// The root argument specifies the root directory from which to serve static assets.
|
||||
//
|
||||
// Note: Root has to be string or fs.FS, otherwise it will panic.
|
||||
func New(root string, cfg ...Config) fiber.Handler {
|
||||
config := configDefault(cfg...)
|
||||
|
||||
var createFS sync.Once
|
||||
var fileHandler fasthttp.RequestHandler
|
||||
var cacheControlValue string
|
||||
|
||||
// adjustments for io/fs compatibility
|
||||
if config.FS != nil && root == "" {
|
||||
root = "."
|
||||
}
|
||||
|
||||
return func(c fiber.Ctx) error {
|
||||
// Don't execute middleware if Next returns true
|
||||
if config.Next != nil && config.Next(c) {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// We only serve static assets on GET or HEAD methods
|
||||
method := c.Method()
|
||||
if method != fiber.MethodGet && method != fiber.MethodHead {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Initialize FS
|
||||
createFS.Do(func() {
|
||||
prefix := c.Route().Path
|
||||
|
||||
// Is prefix a partial wildcard?
|
||||
if strings.Contains(prefix, "*") {
|
||||
// /john* -> /john
|
||||
prefix = strings.Split(prefix, "*")[0]
|
||||
}
|
||||
|
||||
prefixLen := len(prefix)
|
||||
if prefixLen > 1 && prefix[prefixLen-1:] == "/" {
|
||||
// /john/ -> /john
|
||||
prefixLen--
|
||||
}
|
||||
|
||||
fs := &fasthttp.FS{
|
||||
Root: root,
|
||||
FS: config.FS,
|
||||
AllowEmptyRoot: true,
|
||||
GenerateIndexPages: config.Browse,
|
||||
AcceptByteRange: config.ByteRange,
|
||||
Compress: config.Compress,
|
||||
CompressedFileSuffix: c.App().Config().CompressedFileSuffix,
|
||||
CacheDuration: config.CacheDuration,
|
||||
SkipCache: config.CacheDuration < 0,
|
||||
IndexNames: config.IndexNames,
|
||||
PathNotFound: func(fctx *fasthttp.RequestCtx) {
|
||||
fctx.Response.SetStatusCode(fiber.StatusNotFound)
|
||||
},
|
||||
}
|
||||
|
||||
fs.PathRewrite = func(fctx *fasthttp.RequestCtx) []byte {
|
||||
path := fctx.Path()
|
||||
|
||||
if len(path) >= prefixLen {
|
||||
checkFile, err := isFile(root, fs.FS)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
|
||||
// If the root is a file, we need to reset the path to "/" always.
|
||||
switch {
|
||||
case checkFile && fs.FS == nil:
|
||||
path = []byte("/")
|
||||
case checkFile && fs.FS != nil:
|
||||
path = utils.UnsafeBytes(root)
|
||||
default:
|
||||
path = path[prefixLen:]
|
||||
if len(path) == 0 || path[len(path)-1] != '/' {
|
||||
path = append(path, '/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(path) > 0 && path[0] != '/' {
|
||||
path = append([]byte("/"), path...)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
maxAge := config.MaxAge
|
||||
if maxAge > 0 {
|
||||
cacheControlValue = "public, max-age=" + strconv.Itoa(maxAge)
|
||||
}
|
||||
|
||||
fileHandler = fs.NewRequestHandler()
|
||||
})
|
||||
|
||||
// Serve file
|
||||
fileHandler(c.Context())
|
||||
|
||||
// Sets the response Content-Disposition header to attachment if the Download option is true
|
||||
if config.Download {
|
||||
c.Attachment()
|
||||
}
|
||||
|
||||
// Return request if found and not forbidden
|
||||
status := c.Context().Response.StatusCode()
|
||||
if status != fiber.StatusNotFound && status != fiber.StatusForbidden {
|
||||
if len(cacheControlValue) > 0 {
|
||||
c.Context().Response.Header.Set(fiber.HeaderCacheControl, cacheControlValue)
|
||||
}
|
||||
|
||||
if config.ModifyResponse != nil {
|
||||
return config.ModifyResponse(c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return custom 404 handler if provided.
|
||||
if config.NotFoundHandler != nil {
|
||||
return config.NotFoundHandler(c)
|
||||
}
|
||||
|
||||
// Reset response to default
|
||||
c.Context().SetContentType("") // Issue #420
|
||||
c.Context().Response.SetStatusCode(fiber.StatusOK)
|
||||
c.Context().Response.SetBodyString("")
|
||||
|
||||
// Next middleware
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// isFile checks if the root is a file.
|
||||
func isFile(root string, filesystem fs.FS) (bool, error) {
|
||||
var file fs.File
|
||||
var err error
|
||||
|
||||
if filesystem != nil {
|
||||
file, err = filesystem.Open(root)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("static: %w", err)
|
||||
}
|
||||
} else {
|
||||
file, err = os.Open(filepath.Clean(root))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("static: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("static: %w", err)
|
||||
}
|
||||
|
||||
return stat.Mode().IsRegular(), nil
|
||||
}
|
721
middleware/static/static_test.go
Normal file
721
middleware/static/static_test.go
Normal file
@ -0,0 +1,721 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// go test -run Test_Static_Index_Default
|
||||
func Test_Static_Index_Default(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/prefix", New("../../.github/workflows"))
|
||||
|
||||
app.Get("", New("../../.github/"))
|
||||
|
||||
app.Get("test", New("", Config{
|
||||
IndexNames: []string{"index.html"},
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/not-found", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextPlainCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Cannot GET /not-found", string(body))
|
||||
}
|
||||
|
||||
// go test -run Test_Static_Index
|
||||
func Test_Static_Direct(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/*", New("../../.github"))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 405, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextPlainCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/testdata/testRoutes.json", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMEApplicationJSON, resp.Header.Get("Content-Type"))
|
||||
require.Equal(t, "", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "test_routes")
|
||||
}
|
||||
|
||||
// go test -run Test_Static_MaxAge
|
||||
func Test_Static_MaxAge(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/*", New("../../.github", Config{
|
||||
MaxAge: 100,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get(fiber.HeaderContentType))
|
||||
require.Equal(t, "public, max-age=100", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
|
||||
}
|
||||
|
||||
// go test -run Test_Static_Custom_CacheControl
|
||||
func Test_Static_Custom_CacheControl(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/*", New("../../.github", Config{
|
||||
ModifyResponse: func(c fiber.Ctx) error {
|
||||
if strings.Contains(c.GetRespHeader("Content-Type"), "text/html") {
|
||||
c.Response().Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/index.html", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, "no-cache, no-store, must-revalidate", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
|
||||
|
||||
normalResp, normalErr := app.Test(httptest.NewRequest(fiber.MethodGet, "/config.yml", nil))
|
||||
require.NoError(t, normalErr, "app.Test(req)")
|
||||
require.Equal(t, "", normalResp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
|
||||
}
|
||||
|
||||
func Test_Static_Disable_Cache(t *testing.T) {
|
||||
// Skip on Windows. It's not possible to delete a file that is in use.
|
||||
if runtime.GOOS == "windows" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
file, err := os.Create("../../.github/test.txt")
|
||||
require.NoError(t, err)
|
||||
_, err = file.WriteString("Hello, World!")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
// Remove the file even if the test fails
|
||||
defer func() {
|
||||
_ = os.Remove("../../.github/test.txt") //nolint:errcheck // not needed
|
||||
}()
|
||||
|
||||
app.Get("/*", New("../../.github/", Config{
|
||||
CacheDuration: -1,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test.txt", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, "", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
|
||||
require.NoError(t, os.Remove("../../.github/test.txt"))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/test.txt", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, "", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Cannot GET /test.txt", string(body))
|
||||
}
|
||||
|
||||
func Test_Static_NotFoundHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/*", New("../../.github", Config{
|
||||
NotFoundHandler: func(c fiber.Ctx) error {
|
||||
return c.SendString("Custom 404")
|
||||
},
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/not-found", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextPlainCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Custom 404", string(body))
|
||||
}
|
||||
|
||||
// go test -run Test_Static_Download
|
||||
func Test_Static_Download(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/fiber.png", New("../../.github/testdata/fs/img/fiber.png", Config{
|
||||
Download: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/fiber.png", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, "image/png", resp.Header.Get(fiber.HeaderContentType))
|
||||
require.Equal(t, `attachment`, resp.Header.Get(fiber.HeaderContentDisposition))
|
||||
}
|
||||
|
||||
// go test -run Test_Static_Group
|
||||
func Test_Static_Group(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
grp := app.Group("/v1", func(c fiber.Ctx) error {
|
||||
c.Set("Test-Header", "123")
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
grp.Get("/v2*", New("../../.github/index.html"))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/v1/v2", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
require.Equal(t, "123", resp.Header.Get("Test-Header"))
|
||||
|
||||
grp = app.Group("/v2")
|
||||
grp.Get("/v3*", New("../../.github/index.html"))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/v2/v3/john/doe", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
}
|
||||
|
||||
func Test_Static_Wildcard(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("*", New("../../.github/index.html"))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/yesyes/john/doe", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Test file")
|
||||
}
|
||||
|
||||
func Test_Static_Prefix_Wildcard(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/test*", New("../../.github/index.html"))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/test/john/doe", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
app.Get("/my/nameisjohn*", New("../../.github/index.html"))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/my/nameisjohn/no/its/not", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Test file")
|
||||
}
|
||||
|
||||
func Test_Static_Prefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
app.Get("/john*", New("../../.github"))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/john/index.html", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
app.Get("/prefix*", New("../../.github/testdata"))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/prefix/index.html", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
app.Get("/single*", New("../../.github/testdata/testRoutes.json"))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/single", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMEApplicationJSON, resp.Header.Get(fiber.HeaderContentType))
|
||||
}
|
||||
|
||||
func Test_Static_Trailing_Slash(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
app.Get("/john*", New("../../.github"))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/john/", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
app.Get("/john_without_index*", New("../../.github/testdata/fs/css"))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/john_without_index/", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextPlainCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
app.Use("/john", New("../../.github"))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/john/", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/john", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
app.Use("/john_without_index/", New("../../.github/testdata/fs/css"))
|
||||
|
||||
req = httptest.NewRequest(fiber.MethodGet, "/john_without_index/", nil)
|
||||
resp, err = app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextPlainCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
}
|
||||
|
||||
func Test_Static_Next(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/*", New("../../.github", Config{
|
||||
Next: func(c fiber.Ctx) bool {
|
||||
return c.Get("X-Custom-Header") == "skip"
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/*", func(c fiber.Ctx) error {
|
||||
return c.SendString("You've skipped app.Static")
|
||||
})
|
||||
|
||||
t.Run("app.Static is skipped: invoking Get handler", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Set("X-Custom-Header", "skip")
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextPlainCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "You've skipped app.Static")
|
||||
})
|
||||
|
||||
t.Run("app.Static is not skipped: serving index.html", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Set("X-Custom-Header", "don't skip")
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Hello, World!")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Route_Static_Root(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := "../../.github/testdata/fs/css"
|
||||
app := fiber.New()
|
||||
app.Get("/*", New(dir, Config{
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
|
||||
app = fiber.New()
|
||||
app.Get("/*", New(dir))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
}
|
||||
|
||||
func Test_Route_Static_HasPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := "../../.github/testdata/fs/css"
|
||||
app := fiber.New()
|
||||
app.Get("/static*", New(dir, Config{
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
|
||||
app = fiber.New()
|
||||
app.Get("/static/*", New(dir, Config{
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
|
||||
app = fiber.New()
|
||||
app.Get("/static*", New(dir))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
|
||||
app = fiber.New()
|
||||
app.Get("/static*", New(dir))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
}
|
||||
|
||||
func Test_Static_FS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/*", New("", Config{
|
||||
FS: os.DirFS("../../.github/testdata/fs"),
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/css/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextCSSCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
}
|
||||
|
||||
/*func Test_Static_FS_DifferentRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/*", New("fs", Config{
|
||||
FS: os.DirFS("../../.github/testdata"),
|
||||
IndexNames: []string{"index2.html"},
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "<h1>Hello, World!</h1>")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/css/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextCSSCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
}*/
|
||||
|
||||
//go:embed static.go config.go
|
||||
var fsTestFilesystem embed.FS
|
||||
|
||||
func Test_Static_FS_Browse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/embed*", New("", Config{
|
||||
FS: fsTestFilesystem,
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
app.Get("/dirfs*", New("", Config{
|
||||
FS: os.DirFS("../../.github/testdata/fs/css"),
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/dirfs", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "style.css")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/dirfs/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextCSSCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "color")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/embed", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, string(body), "static.go")
|
||||
}
|
||||
|
||||
func Test_Static_FS_Prefix_Wildcard(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/test*", New("index.html", Config{
|
||||
FS: os.DirFS("../../.github"),
|
||||
IndexNames: []string{"not_index.html"},
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/test/john/doe", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
require.NotEmpty(t, resp.Header.Get(fiber.HeaderContentLength))
|
||||
require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "Test file")
|
||||
}
|
||||
|
||||
func Test_isFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
filesystem fs.FS
|
||||
expected bool
|
||||
gotError error
|
||||
}{
|
||||
{
|
||||
name: "file",
|
||||
path: "index.html",
|
||||
filesystem: os.DirFS("../../.github"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
path: "index2.html",
|
||||
filesystem: os.DirFS("../../.github"),
|
||||
expected: false,
|
||||
gotError: fs.ErrNotExist,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
path: ".",
|
||||
filesystem: os.DirFS("../../.github"),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
path: "not_exists",
|
||||
filesystem: os.DirFS("../../.github"),
|
||||
expected: false,
|
||||
gotError: fs.ErrNotExist,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
path: ".",
|
||||
filesystem: os.DirFS("../../.github/testdata/fs/css"),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
path: "../../.github/testdata/fs/css/style.css",
|
||||
filesystem: nil,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
path: "../../.github/testdata/fs/css/style2.css",
|
||||
filesystem: nil,
|
||||
expected: false,
|
||||
gotError: fs.ErrNotExist,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
path: "../../.github/testdata/fs/css",
|
||||
filesystem: nil,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
c := c
|
||||
t.Parallel()
|
||||
|
||||
actual, err := isFile(c.path, c.filesystem)
|
||||
require.ErrorIs(t, err, c.gotError)
|
||||
require.Equal(t, c.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
@ -19,8 +19,6 @@ type Register interface {
|
||||
|
||||
Add(methods []string, handler Handler, middleware ...Handler) Register
|
||||
|
||||
Static(root string, config ...Static) Register
|
||||
|
||||
Route(path string) Register
|
||||
}
|
||||
|
||||
@ -112,12 +110,6 @@ func (r *Registering) Add(methods []string, handler Handler, middleware ...Handl
|
||||
return r
|
||||
}
|
||||
|
||||
// Static will create a file server serving static files
|
||||
func (r *Registering) Static(root string, config ...Static) Register {
|
||||
r.app.registerStatic(r.path, root, config...)
|
||||
return r
|
||||
}
|
||||
|
||||
// Route returns a new Register instance whose route path takes
|
||||
// the path in the current instance as its prefix.
|
||||
func (r *Registering) Route(path string) Register {
|
||||
|
141
router.go
141
router.go
@ -9,10 +9,8 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/utils/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
@ -33,7 +31,6 @@ type Router interface {
|
||||
Patch(path string, handler Handler, middleware ...Handler) Router
|
||||
|
||||
Add(methods []string, path string, handler Handler, middleware ...Handler) Router
|
||||
Static(prefix, root string, config ...Static) Router
|
||||
All(path string, handler Handler, middleware ...Handler) Router
|
||||
|
||||
Group(prefix string, handlers ...Handler) Router
|
||||
@ -377,144 +374,6 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) registerStatic(prefix, root string, config ...Static) {
|
||||
// For security, we want to restrict to the current work directory.
|
||||
if root == "" {
|
||||
root = "."
|
||||
}
|
||||
// Cannot have an empty prefix
|
||||
if prefix == "" {
|
||||
prefix = "/"
|
||||
}
|
||||
// Prefix always start with a '/' or '*'
|
||||
if prefix[0] != '/' {
|
||||
prefix = "/" + prefix
|
||||
}
|
||||
// in case-sensitive routing, all to lowercase
|
||||
if !app.config.CaseSensitive {
|
||||
prefix = utils.ToLower(prefix)
|
||||
}
|
||||
// Strip trailing slashes from the root path
|
||||
if len(root) > 0 && root[len(root)-1] == '/' {
|
||||
root = root[:len(root)-1]
|
||||
}
|
||||
// Is prefix a direct wildcard?
|
||||
isStar := prefix == "/*"
|
||||
// Is prefix a root slash?
|
||||
isRoot := prefix == "/"
|
||||
// Is prefix a partial wildcard?
|
||||
if strings.Contains(prefix, "*") {
|
||||
// /john* -> /john
|
||||
isStar = true
|
||||
prefix = strings.Split(prefix, "*")[0]
|
||||
// Fix this later
|
||||
}
|
||||
prefixLen := len(prefix)
|
||||
if prefixLen > 1 && prefix[prefixLen-1:] == "/" {
|
||||
// /john/ -> /john
|
||||
prefixLen--
|
||||
prefix = prefix[:prefixLen]
|
||||
}
|
||||
const cacheDuration = 10 * time.Second
|
||||
// Fileserver settings
|
||||
fs := &fasthttp.FS{
|
||||
Root: root,
|
||||
AllowEmptyRoot: true,
|
||||
GenerateIndexPages: false,
|
||||
AcceptByteRange: false,
|
||||
Compress: false,
|
||||
CompressedFileSuffix: app.config.CompressedFileSuffix,
|
||||
CacheDuration: cacheDuration,
|
||||
IndexNames: []string{"index.html"},
|
||||
PathRewrite: func(fctx *fasthttp.RequestCtx) []byte {
|
||||
path := fctx.Path()
|
||||
if len(path) >= prefixLen {
|
||||
if isStar && app.getString(path[0:prefixLen]) == prefix {
|
||||
path = append(path[0:0], '/')
|
||||
} else {
|
||||
path = path[prefixLen:]
|
||||
if len(path) == 0 || path[len(path)-1] != '/' {
|
||||
path = append(path, '/')
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(path) > 0 && path[0] != '/' {
|
||||
path = append([]byte("/"), path...)
|
||||
}
|
||||
return path
|
||||
},
|
||||
PathNotFound: func(fctx *fasthttp.RequestCtx) {
|
||||
fctx.Response.SetStatusCode(StatusNotFound)
|
||||
},
|
||||
}
|
||||
|
||||
// Set config if provided
|
||||
var cacheControlValue string
|
||||
var modifyResponse Handler
|
||||
if len(config) > 0 {
|
||||
maxAge := config[0].MaxAge
|
||||
if maxAge > 0 {
|
||||
cacheControlValue = "public, max-age=" + strconv.Itoa(maxAge)
|
||||
}
|
||||
fs.CacheDuration = config[0].CacheDuration
|
||||
fs.Compress = config[0].Compress
|
||||
fs.AcceptByteRange = config[0].ByteRange
|
||||
fs.GenerateIndexPages = config[0].Browse
|
||||
if config[0].Index != "" {
|
||||
fs.IndexNames = []string{config[0].Index}
|
||||
}
|
||||
modifyResponse = config[0].ModifyResponse
|
||||
}
|
||||
fileHandler := fs.NewRequestHandler()
|
||||
handler := func(c Ctx) error {
|
||||
// Don't execute middleware if Next returns true
|
||||
if len(config) != 0 && config[0].Next != nil && config[0].Next(c) {
|
||||
return c.Next()
|
||||
}
|
||||
// Serve file
|
||||
fileHandler(c.Context())
|
||||
// Sets the response Content-Disposition header to attachment if the Download option is true
|
||||
if len(config) > 0 && config[0].Download {
|
||||
c.Attachment()
|
||||
}
|
||||
// Return request if found and not forbidden
|
||||
status := c.Context().Response.StatusCode()
|
||||
if status != StatusNotFound && status != StatusForbidden {
|
||||
if len(cacheControlValue) > 0 {
|
||||
c.Context().Response.Header.Set(HeaderCacheControl, cacheControlValue)
|
||||
}
|
||||
if modifyResponse != nil {
|
||||
return modifyResponse(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Reset response to default
|
||||
c.Context().SetContentType("") // Issue #420
|
||||
c.Context().Response.SetStatusCode(StatusOK)
|
||||
c.Context().Response.SetBodyString("")
|
||||
// Next middleware
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Create route metadata without pointer
|
||||
route := Route{
|
||||
// Router booleans
|
||||
use: true,
|
||||
root: isRoot,
|
||||
path: prefix,
|
||||
// Public data
|
||||
Method: MethodGet,
|
||||
Path: prefix,
|
||||
Handlers: []Handler{handler},
|
||||
}
|
||||
// Increment global handler count
|
||||
atomic.AddUint32(&app.handlersCount, 1)
|
||||
// Add route to stack
|
||||
app.addRoute(MethodGet, &route)
|
||||
// Add HEAD route
|
||||
app.addRoute(MethodHead, &route)
|
||||
}
|
||||
|
||||
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||
// Check mounted routes
|
||||
var mounted bool
|
||||
|
122
router_test.go
122
router_test.go
@ -332,128 +332,6 @@ func Test_Router_Handler_Catch_Error(t *testing.T) {
|
||||
require.Equal(t, StatusInternalServerError, c.Response.Header.StatusCode())
|
||||
}
|
||||
|
||||
func Test_Route_Static_Root(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := "./.github/testdata/fs/css"
|
||||
app := New()
|
||||
app.Static("/", dir, Static{
|
||||
Browse: true,
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, app.getString(body), "color")
|
||||
|
||||
app = New()
|
||||
app.Static("/", dir)
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, app.getString(body), "color")
|
||||
}
|
||||
|
||||
func Test_Route_Static_HasPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := "./.github/testdata/fs/css"
|
||||
app := New()
|
||||
app.Static("/static", dir, Static{
|
||||
Browse: true,
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, app.getString(body), "color")
|
||||
|
||||
app = New()
|
||||
app.Static("/static/", dir, Static{
|
||||
Browse: true,
|
||||
})
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, app.getString(body), "color")
|
||||
|
||||
app = New()
|
||||
app.Static("/static", dir)
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, app.getString(body), "color")
|
||||
|
||||
app = New()
|
||||
app.Static("/static/", dir)
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 404, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static/style.css", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, 200, resp.StatusCode, "Status code")
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Contains(t, app.getString(body), "color")
|
||||
}
|
||||
|
||||
func Test_Router_NotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
Loading…
x
Reference in New Issue
Block a user