Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ v3 (enhancement): refactor filesystem middleware with io/fs #2027

Merged
merged 5 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 7 additions & 146 deletions middleware/filesystem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -88,153 +83,21 @@ 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,
}))

log.Fatal(app.Listen(":3000"))
}
```

### 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
Expand All @@ -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.
Expand Down Expand Up @@ -288,7 +149,7 @@ type Config struct {
var ConfigDefault = Config{
Next: nil,
Root: nil,
PathPrefix: "",
PathPrefix: ".",
Browse: false,
Index: "/index.html",
MaxAge: 0,
Expand Down
66 changes: 46 additions & 20 deletions middleware/filesystem/filesystem.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package filesystem

import (
"io/fs"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
Expand All @@ -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.
Expand All @@ -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.
//
Expand All @@ -57,7 +62,7 @@ type Config struct {
var ConfigDefault = Config{
Next: nil,
Root: nil,
PathPrefix: "",
PathPrefix: ".",
Browse: false,
Index: "/index.html",
MaxAge: 0,
Expand All @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -120,23 +133,26 @@ func New(config ...Config) fiber.Handler {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Add PathPrefix
if cfg.PathPrefix != "" {
// PathPrefix already has a "/" prefix
path = cfg.PathPrefix + path
}

var (
file http.File
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 = 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 {
Expand All @@ -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 {
Expand All @@ -168,6 +186,7 @@ func New(config ...Config) fiber.Handler {
if cfg.Browse {
return dirList(c, file)
}

return fiber.ErrForbidden
}

Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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 {
Expand Down
Loading