mirror of https://github.com/gofiber/fiber.git
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>pull/3024/head
parent
fca62c1853
commit
38fb8064c6
|
@ -203,15 +203,15 @@ func main() {
|
||||||
func main() {
|
func main() {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Static("/", "./public")
|
app.Get("/*", static.New("./public"))
|
||||||
// => http://localhost:3000/js/script.js
|
// => http://localhost:3000/js/script.js
|
||||||
// => http://localhost:3000/css/style.css
|
// => 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/js/script.js
|
||||||
// => http://localhost:3000/prefix/css/style.css
|
// => 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
|
// => http://localhost:3000/any/path/shows/index/html
|
||||||
|
|
||||||
log.Fatal(app.Listen(":3000"))
|
log.Fatal(app.Listen(":3000"))
|
||||||
|
@ -388,7 +388,7 @@ curl -H "Origin: http://example.com" --verbose http://localhost:3000
|
||||||
func main() {
|
func main() {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Static("/", "./public")
|
app.Get("/", static.New("./public"))
|
||||||
|
|
||||||
app.Get("/demo", func(c fiber.Ctx) error {
|
app.Get("/demo", func(c fiber.Ctx) error {
|
||||||
return c.SendString("This is a demo!")
|
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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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
|
## 🧬 External Middleware
|
||||||
|
|
53
app.go
53
app.go
|
@ -381,52 +381,6 @@ type Config struct {
|
||||||
EnableSplittingOnParsers bool `json:"enable_splitting_on_parsers"`
|
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
|
// RouteMessage is some message need to be print when server starts
|
||||||
type RouteMessage struct {
|
type RouteMessage struct {
|
||||||
name string
|
name string
|
||||||
|
@ -780,13 +734,6 @@ func (app *App) Add(methods []string, path string, handler Handler, middleware .
|
||||||
return app
|
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
|
// All will register the handler on all HTTP methods
|
||||||
func (app *App) All(path string, handler Handler, middleware ...Handler) Router {
|
func (app *App) All(path string, handler Handler, middleware ...Handler) Router {
|
||||||
return app.Add(app.config.RequestMethods, path, handler, middleware...)
|
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
|
// go test -run Test_App_Mixed_Routes_WithSameLen
|
||||||
func Test_App_Mixed_Routes_WithSameLen(t *testing.T) {
|
func Test_App_Mixed_Routes_WithSameLen(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -1220,7 +912,10 @@ func Test_App_Mixed_Routes_WithSameLen(t *testing.T) {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
})
|
})
|
||||||
// routes with the same length
|
// 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 {
|
app.Get("/foobar", func(c Ctx) error {
|
||||||
c.Type("html")
|
c.Type("html")
|
||||||
return c.Send([]byte("FOO_BAR"))
|
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.Equal(t, 200, resp.StatusCode, "Status code")
|
||||||
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
require.NotEmpty(t, resp.Header.Get(HeaderContentLength))
|
||||||
require.Equal(t, "TestValue", resp.Header.Get("TestHeader"))
|
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)
|
body, err = io.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Contains(t, string(body), "Hello, World!")
|
require.Contains(t, string(body), "TEST_BAR")
|
||||||
require.True(t, strings.HasPrefix(string(body), "<!DOCTYPE html>"), "Response: "+string(body))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_App_Group_Invalid(t *testing.T) {
|
func Test_App_Group_Invalid(t *testing.T) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ const (
|
||||||
MIMETextHTML = "text/html"
|
MIMETextHTML = "text/html"
|
||||||
MIMETextPlain = "text/plain"
|
MIMETextPlain = "text/plain"
|
||||||
MIMETextJavaScript = "text/javascript"
|
MIMETextJavaScript = "text/javascript"
|
||||||
|
MIMETextCSS = "text/css"
|
||||||
MIMEApplicationXML = "application/xml"
|
MIMEApplicationXML = "application/xml"
|
||||||
MIMEApplicationJSON = "application/json"
|
MIMEApplicationJSON = "application/json"
|
||||||
// Deprecated: use MIMETextJavaScript instead
|
// Deprecated: use MIMETextJavaScript instead
|
||||||
|
@ -32,6 +33,7 @@ const (
|
||||||
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
||||||
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
|
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
|
||||||
MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
|
MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
|
||||||
|
MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8"
|
||||||
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
|
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
|
||||||
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
||||||
// Deprecated: use MIMETextJavaScriptCharsetUTF8 instead
|
// Deprecated: use MIMETextJavaScriptCharsetUTF8 instead
|
||||||
|
|
|
@ -11,70 +11,6 @@ import Reference from '@site/src/components/reference';
|
||||||
|
|
||||||
import RoutingHandler from './../partials/routing/handler.md';
|
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
|
### Route Handlers
|
||||||
|
|
||||||
<RoutingHandler />
|
<RoutingHandler />
|
||||||
|
@ -181,8 +117,6 @@ type Register interface {
|
||||||
|
|
||||||
Add(methods []string, handler Handler, middleware ...Handler) Register
|
Add(methods []string, handler Handler, middleware ...Handler) Register
|
||||||
|
|
||||||
Static(root string, config ...Static) Register
|
|
||||||
|
|
||||||
Route(path string) Register
|
Route(path string) Register
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -29,6 +29,8 @@ const (
|
||||||
MIMETextXML = "text/xml"
|
MIMETextXML = "text/xml"
|
||||||
MIMETextHTML = "text/html"
|
MIMETextHTML = "text/html"
|
||||||
MIMETextPlain = "text/plain"
|
MIMETextPlain = "text/plain"
|
||||||
|
MIMETextJavaScript = "text/javascript"
|
||||||
|
MIMETextCSS = "text/css"
|
||||||
MIMEApplicationXML = "application/xml"
|
MIMEApplicationXML = "application/xml"
|
||||||
MIMEApplicationJSON = "application/json"
|
MIMEApplicationJSON = "application/json"
|
||||||
MIMEApplicationJavaScript = "application/javascript"
|
MIMEApplicationJavaScript = "application/javascript"
|
||||||
|
@ -39,11 +41,12 @@ const (
|
||||||
MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
|
MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
|
||||||
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
|
||||||
MIMETextPlainCharsetUTF8 = "text/plain; 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"
|
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
|
||||||
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
|
||||||
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
|
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
|
||||||
)
|
)```
|
||||||
```
|
|
||||||
|
|
||||||
### HTTP status codes were copied from net/http.
|
### HTTP status codes were copied from net/http.
|
||||||
|
|
||||||
|
|
|
@ -164,19 +164,15 @@ app.Get("/api/*", func(c fiber.Ctx) error {
|
||||||
### Static files
|
### Static files
|
||||||
|
|
||||||
To serve static files such as **images**, **CSS**, and **JavaScript** files, replace your function handler with a file or directory string.
|
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:
|
Function signature:
|
||||||
|
|
||||||
```go
|
|
||||||
app.Static(prefix, root string, config ...Static)
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the following code to serve files in a directory named `./public`:
|
Use the following code to serve files in a directory named `./public`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Static("/", "./public")
|
app.Get("/*", static.New("./public"))
|
||||||
|
|
||||||
app.Listen(":3000")
|
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")
|
|
||||||
})
|
|
||||||
```
|
|
|
@ -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:
|
We have made several changes to the Fiber app, including:
|
||||||
|
|
||||||
* Listen -> unified with config
|
* Listen -> unified with config
|
||||||
|
* Static -> has been removed and moved to [static middleware](./middleware/static.md)
|
||||||
* app.Config properties moved to listen config
|
* app.Config properties moved to listen config
|
||||||
* DisableStartupMessage
|
* DisableStartupMessage
|
||||||
* EnablePrefork -> previously Prefork
|
* EnablePrefork -> previously Prefork
|
||||||
|
@ -270,9 +271,8 @@ DRAFT section
|
||||||
|
|
||||||
### Filesystem
|
### Filesystem
|
||||||
|
|
||||||
:::caution
|
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
||||||
DRAFT section
|
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
|
### Monitor
|
||||||
|
|
||||||
|
@ -295,6 +295,34 @@ Monitor middleware is now in Contrib package.
|
||||||
|
|
||||||
### 🚀 App
|
### 🚀 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
|
### 🗺 Router
|
||||||
|
|
||||||
### 🧠 Context
|
### 🧠 Context
|
||||||
|
@ -328,4 +356,35 @@ app.Use(cors.New(cors.Config{
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
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
|
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
|
// All will register the handler on all HTTP methods
|
||||||
func (grp *Group) All(path string, handler Handler, middleware ...Handler) Router {
|
func (grp *Group) All(path string, handler Handler, middleware ...Handler) Router {
|
||||||
_ = grp.Add(grp.app.config.RequestMethods, path, handler, middleware...)
|
_ = 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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
Add(methods []string, handler Handler, middleware ...Handler) Register
|
||||||
|
|
||||||
Static(root string, config ...Static) Register
|
|
||||||
|
|
||||||
Route(path string) Register
|
Route(path string) Register
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,12 +110,6 @@ func (r *Registering) Add(methods []string, handler Handler, middleware ...Handl
|
||||||
return r
|
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
|
// Route returns a new Register instance whose route path takes
|
||||||
// the path in the current instance as its prefix.
|
// the path in the current instance as its prefix.
|
||||||
func (r *Registering) Route(path string) Register {
|
func (r *Registering) Route(path string) Register {
|
||||||
|
|
141
router.go
141
router.go
|
@ -9,10 +9,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofiber/utils/v2"
|
"github.com/gofiber/utils/v2"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
@ -33,7 +31,6 @@ type Router interface {
|
||||||
Patch(path string, handler Handler, middleware ...Handler) Router
|
Patch(path string, handler Handler, middleware ...Handler) Router
|
||||||
|
|
||||||
Add(methods []string, 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
|
All(path string, handler Handler, middleware ...Handler) Router
|
||||||
|
|
||||||
Group(prefix string, handlers ...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) {
|
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||||
// Check mounted routes
|
// Check mounted routes
|
||||||
var mounted bool
|
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())
|
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) {
|
func Test_Router_NotFound(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
|
|
Loading…
Reference in New Issue