mirror of https://github.com/gofiber/fiber.git
✨ v3 (enhancement): refactor filesystem middleware with `io/fs` (#2027)
* ✨ v3 (feature): refactor filesystem middleware with `io/fs`
* update docs
* attachment support.
* fix
pull/2130/head
parent
01cfc64f1c
commit
668b0c85f8
|
@ -12,11 +12,6 @@ Filesystem middleware for [Fiber](https://github.com/gofiber/fiber) that enables
|
|||
- [Examples](#examples)
|
||||
- [Config](#config)
|
||||
- [embed](#embed)
|
||||
- [pkger](#pkger)
|
||||
- [packr](#packr)
|
||||
- [go.rice](#gorice)
|
||||
- [fileb0x](#fileb0x)
|
||||
- [statik](#statik)
|
||||
- [Config](#config-1)
|
||||
- [Default Config](#default-config)
|
||||
|
||||
|
@ -44,12 +39,12 @@ Then create a Fiber app with `app := fiber.New()`.
|
|||
```go
|
||||
// Provide a minimal config
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.Dir("./assets"),
|
||||
Root: os.DirFS("./assets"),
|
||||
}))
|
||||
|
||||
// Or extend your config for customization
|
||||
app.Use(filesystem.New(filesystem.Config{
|
||||
Root: http.Dir("./assets"),
|
||||
Root: os.DirFS("./assets"),
|
||||
Browse: true,
|
||||
Index: "index.html",
|
||||
NotFoundFile: "404.html",
|
||||
|
@ -88,15 +83,14 @@ func main() {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(f),
|
||||
Root: 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",
|
||||
Root: embedDirStatic,
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
|
@ -104,137 +98,6 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
### pkger
|
||||
|
||||
[Pkger](https://github.com/markbates/pkger) can be used to embed files in a Golang excecutable.
|
||||
|
||||
```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
|
||||
|
||||
[Packr](https://github.com/gobuffalo/packr) can be used to embed files in a Golang excecutable.
|
||||
|
||||
```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
|
||||
|
||||
```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
|
||||
|
||||
[Fileb0x](https://github.com/UnnoTed/fileb0x) can be used to embed files in a Golang excecutable.
|
||||
|
||||
```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
|
||||
|
||||
[Statik](https://github.com/rakyll/statik) can be used to embed files in a Golang excecutable.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/filesystem"
|
||||
|
||||
"<Your go module>/statik"
|
||||
fs "github.com/rakyll/statik/fs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
statik, 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
|
||||
|
||||
```go
|
||||
|
@ -249,14 +112,12 @@ type Config struct {
|
|||
// to a collection of files and directories.
|
||||
//
|
||||
// Required. Default: nil
|
||||
Root http.FileSystem `json:"-"`
|
||||
Root fs.FS `json:"-"`
|
||||
|
||||
// PathPrefix defines a prefix to be added to a filepath when
|
||||
// reading a file from the FileSystem.
|
||||
//
|
||||
// Use when using Go 1.16 embed.FS
|
||||
//
|
||||
// Optional. Default ""
|
||||
// Optional. Default "."
|
||||
PathPrefix string `json:"path_prefix"`
|
||||
|
||||
// Enable directory browsing.
|
||||
|
@ -288,7 +149,7 @@ type Config struct {
|
|||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Root: nil,
|
||||
PathPrefix: "",
|
||||
PathPrefix: ".",
|
||||
Browse: false,
|
||||
Index: "/index.html",
|
||||
MaxAge: 0,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package filesystem
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -21,14 +23,12 @@ type Config struct {
|
|||
// to a collection of files and directories.
|
||||
//
|
||||
// Required. Default: nil
|
||||
Root http.FileSystem `json:"-"`
|
||||
Root fs.FS `json:"-"`
|
||||
|
||||
// PathPrefix defines a prefix to be added to a filepath when
|
||||
// reading a file from the FileSystem.
|
||||
//
|
||||
// Use when using Go 1.16 embed.FS
|
||||
//
|
||||
// Optional. Default ""
|
||||
// Optional. Default "."
|
||||
PathPrefix string `json:"path_prefix"`
|
||||
|
||||
// Enable directory browsing.
|
||||
|
@ -41,6 +41,11 @@ type Config struct {
|
|||
// 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.
|
||||
//
|
||||
|
@ -57,7 +62,7 @@ type Config struct {
|
|||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Root: nil,
|
||||
PathPrefix: "",
|
||||
PathPrefix: ".",
|
||||
Browse: false,
|
||||
Index: "/index.html",
|
||||
MaxAge: 0,
|
||||
|
@ -76,6 +81,9 @@ func New(config ...Config) fiber.Handler {
|
|||
if cfg.Index == "" {
|
||||
cfg.Index = ConfigDefault.Index
|
||||
}
|
||||
if cfg.PathPrefix == "" {
|
||||
cfg.PathPrefix = ConfigDefault.PathPrefix
|
||||
}
|
||||
if !strings.HasPrefix(cfg.Index, "/") {
|
||||
cfg.Index = "/" + cfg.Index
|
||||
}
|
||||
|
@ -88,8 +96,13 @@ func New(config ...Config) fiber.Handler {
|
|||
panic("filesystem: Root cannot be nil")
|
||||
}
|
||||
|
||||
if cfg.PathPrefix != "" && !strings.HasPrefix(cfg.PathPrefix, "/") {
|
||||
cfg.PathPrefix = "/" + cfg.PathPrefix
|
||||
// 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
|
||||
|
@ -120,23 +133,26 @@ func New(config ...Config) fiber.Handler {
|
|||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
var (
|
||||
file fs.File
|
||||
stat os.FileInfo
|
||||
)
|
||||
|
||||
// Add PathPrefix
|
||||
if cfg.PathPrefix != "" {
|
||||
// PathPrefix already has a "/" prefix
|
||||
path = cfg.PathPrefix + path
|
||||
path = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+path))
|
||||
}
|
||||
|
||||
var (
|
||||
file http.File
|
||||
stat os.FileInfo
|
||||
)
|
||||
|
||||
if len(path) > 1 {
|
||||
path = strings.TrimRight(path, "/")
|
||||
}
|
||||
file, err = cfg.Root.Open(path)
|
||||
|
||||
file, err = openFile(cfg.Root, path)
|
||||
|
||||
if err != nil && os.IsNotExist(err) && cfg.NotFoundFile != "" {
|
||||
file, err = cfg.Root.Open(cfg.NotFoundFile)
|
||||
file, err = openFile(cfg.Root, cfg.NotFoundFile)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -153,7 +169,9 @@ func New(config ...Config) fiber.Handler {
|
|||
// Serve index if path is directory
|
||||
if stat.IsDir() {
|
||||
indexPath := strings.TrimRight(path, "/") + cfg.Index
|
||||
index, err := cfg.Root.Open(indexPath)
|
||||
indexPath = filepath.Join(cfg.PathPrefix, filepath.Clean("/"+indexPath))
|
||||
|
||||
index, err := openFile(cfg.Root, indexPath)
|
||||
if err == nil {
|
||||
indexStat, err := index.Stat()
|
||||
if err == nil {
|
||||
|
@ -168,6 +186,7 @@ func New(config ...Config) fiber.Handler {
|
|||
if cfg.Browse {
|
||||
return dirList(c, file)
|
||||
}
|
||||
|
||||
return fiber.ErrForbidden
|
||||
}
|
||||
|
||||
|
@ -182,6 +201,11 @@ func New(config ...Config) fiber.Handler {
|
|||
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)
|
||||
|
@ -205,13 +229,15 @@ func New(config ...Config) fiber.Handler {
|
|||
}
|
||||
|
||||
// SendFile ...
|
||||
func SendFile(c fiber.Ctx, fs http.FileSystem, path string) (err error) {
|
||||
func SendFile(c fiber.Ctx, filesystem fs.FS, path string) (err error) {
|
||||
var (
|
||||
file http.File
|
||||
file fs.File
|
||||
stat os.FileInfo
|
||||
)
|
||||
|
||||
file, err = fs.Open(path)
|
||||
path = filepath.Join(".", filepath.Clean("/"+path))
|
||||
|
||||
file, err = openFile(filesystem, path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fiber.ErrNotFound
|
||||
|
@ -226,7 +252,7 @@ func SendFile(c fiber.Ctx, fs http.FileSystem, path string) (err error) {
|
|||
// Serve index if path is directory
|
||||
if stat.IsDir() {
|
||||
indexPath := strings.TrimRight(path, "/") + ConfigDefault.Index
|
||||
index, err := fs.Open(indexPath)
|
||||
index, err := openFile(filesystem, indexPath)
|
||||
if err == nil {
|
||||
indexStat, err := index.Stat()
|
||||
if err == nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package filesystem
|
|||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
|
@ -14,11 +15,11 @@ func Test_FileSystem(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use("/test", New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
}))
|
||||
|
||||
app.Use("/dir", New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
|
@ -27,13 +28,13 @@ func Test_FileSystem(t *testing.T) {
|
|||
})
|
||||
|
||||
app.Use("/spatest", New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Index: "index.html",
|
||||
NotFoundFile: "index.html",
|
||||
}))
|
||||
|
||||
app.Use("/prefix", New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
PathPrefix: "img",
|
||||
}))
|
||||
|
||||
|
@ -133,7 +134,7 @@ func Test_FileSystem(t *testing.T) {
|
|||
func Test_FileSystem_Next(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
Next: func(_ fiber.Ctx) bool {
|
||||
return true
|
||||
},
|
||||
|
@ -144,11 +145,27 @@ func Test_FileSystem_Next(t *testing.T) {
|
|||
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("GET", "/img/fiber.png", nil))
|
||||
utils.AssertEqual(t, nil, err, "app.Test(req)")
|
||||
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
|
||||
utils.AssertEqual(t, false, resp.Header.Get(fiber.HeaderContentLength) == "")
|
||||
utils.AssertEqual(t, "image/png", resp.Header.Get(fiber.HeaderContentType))
|
||||
utils.AssertEqual(t, `attachment`, resp.Header.Get(fiber.HeaderContentDisposition))
|
||||
}
|
||||
|
||||
func Test_FileSystem_NonGetAndHead(t *testing.T) {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/test", New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/test", nil))
|
||||
|
@ -160,7 +177,7 @@ func Test_FileSystem_Head(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use("/test", New(Config{
|
||||
Root: http.Dir("../../.github/testdata/fs"),
|
||||
Root: os.DirFS("../../.github/testdata/fs"),
|
||||
}))
|
||||
|
||||
req, _ := http.NewRequest(fiber.MethodHead, "/test", nil)
|
||||
|
@ -183,7 +200,7 @@ func Test_FileSystem_UsingParam(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use("/:path", func(c fiber.Ctx) error {
|
||||
return SendFile(c, http.Dir("../../.github/testdata/fs"), c.Params("path")+".html")
|
||||
return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html")
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest(fiber.MethodHead, "/index", nil)
|
||||
|
@ -196,7 +213,7 @@ func Test_FileSystem_UsingParam_NonFile(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use("/:path", func(c fiber.Ctx) error {
|
||||
return SendFile(c, http.Dir("../../.github/testdata/fs"), c.Params("path")+".html")
|
||||
return SendFile(c, os.DirFS("../../.github/testdata/fs"), c.Params("path")+".html")
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest(fiber.MethodHead, "/template", nil)
|
||||
|
|
|
@ -3,9 +3,9 @@ package filesystem
|
|||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"os"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
@ -20,17 +20,23 @@ func getFileExtension(path string) string {
|
|||
return path[n:]
|
||||
}
|
||||
|
||||
func dirList(c fiber.Ctx, f http.File) error {
|
||||
fileinfos, err := f.Readdir(-1)
|
||||
func dirList(c fiber.Ctx, f fs.File) error {
|
||||
ff := f.(fs.ReadDirFile)
|
||||
fileinfos, err := ff.ReadDir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fm := make(map[string]os.FileInfo, len(fileinfos))
|
||||
fm := make(map[string]fs.FileInfo, len(fileinfos))
|
||||
filenames := make([]string, 0, len(fileinfos))
|
||||
for _, fi := range fileinfos {
|
||||
name := fi.Name()
|
||||
fm[name] = fi
|
||||
info, err := fi.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fm[name] = info
|
||||
filenames = append(filenames, name)
|
||||
}
|
||||
|
||||
|
@ -63,3 +69,9 @@ func dirList(c fiber.Ctx, f http.File) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openFile(fs fs.FS, name string) (fs.File, error) {
|
||||
name = filepath.ToSlash(name)
|
||||
|
||||
return fs.Open(name)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue