1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-23 15:03:46 +00:00
fiber/middleware/filesystem/filesystem.go

274 lines
5.7 KiB
Go
Raw Normal View History

2020-09-13 11:20:11 +02:00
package filesystem
import (
"net/http"
"os"
2020-10-27 08:12:37 +01:00
"strconv"
2020-09-13 11:20:11 +02:00
"strings"
"sync"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
2020-09-13 11:20:11 +02:00
)
// 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
2020-10-27 08:12:37 +01:00
Root http.FileSystem `json:"-"`
// PathPrefix defines a prefix to be added to a filepath when
// reading a file from the FileSystem.
//
// Use when using Go 1.16 embed.FS
//
// Optional. Default ""
PathPrefix string `json:"path_prefix"`
2020-10-27 08:12:37 +01:00
// Enable directory browsing.
//
// Optional. Default: false
Browse bool `json:"browse"`
2020-09-13 11:20:11 +02:00
// Index file for serving a directory.
//
// Optional. Default: "index.html"
2020-10-27 08:12:37 +01:00
Index string `json:"index"`
2020-09-13 11:20:11 +02:00
2020-10-27 08:12:37 +01:00
// The value for the Cache-Control HTTP-header
// that is set on the file response. MaxAge is defined in seconds.
2020-09-13 11:20:11 +02:00
//
2020-10-27 08:12:37 +01:00
// Optional. Default value 0.
2020-10-27 08:14:58 +01:00
MaxAge int `json:"max_age"`
2020-09-13 11:20:11 +02:00
// File to return if path is not found. Useful for SPA's.
//
// Optional. Default: ""
2020-10-27 08:12:37 +01:00
NotFoundFile string `json:"not_found_file"`
2020-09-13 11:20:11 +02:00
}
// ConfigDefault is the default config
var ConfigDefault = Config{
Next: nil,
Root: nil,
PathPrefix: "",
Browse: false,
Index: "/index.html",
MaxAge: 0,
2020-09-13 11:20:11 +02:00
}
// New creates a new middleware handler
2020-09-17 12:56:11 +08:00
func New(config ...Config) fiber.Handler {
// Set default config
cfg := ConfigDefault
2020-09-13 11:20:11 +02:00
2020-09-17 12:56:11 +08:00
// Override config if provided
if len(config) > 0 {
cfg = config[0]
// Set default values
if cfg.Index == "" {
cfg.Index = ConfigDefault.Index
}
if !strings.HasPrefix(cfg.Index, "/") {
cfg.Index = "/" + cfg.Index
}
if cfg.NotFoundFile != "" && !strings.HasPrefix(cfg.NotFoundFile, "/") {
cfg.NotFoundFile = "/" + cfg.NotFoundFile
}
2020-09-14 16:12:46 +02:00
}
2020-09-17 12:56:11 +08:00
2020-09-13 11:20:11 +02:00
if cfg.Root == nil {
panic("filesystem: Root cannot be nil")
2020-09-13 11:20:11 +02:00
}
if cfg.PathPrefix != "" && !strings.HasPrefix(cfg.PathPrefix, "/") {
cfg.PathPrefix = "/" + cfg.PathPrefix
}
2020-09-13 11:20:11 +02:00
var once sync.Once
var prefix string
var cacheControlStr = "public, max-age=" + strconv.Itoa(cfg.MaxAge)
2020-09-13 11:20:11 +02:00
// Return new handler
2020-09-17 12:56:11 +08:00
return func(c *fiber.Ctx) (err error) {
2020-09-13 11:20:11 +02:00
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
method := c.Method()
// We only serve static assets on GET or HEAD methods
if method != fiber.MethodGet && method != fiber.MethodHead {
return c.Next()
}
// Set prefix once
once.Do(func() {
prefix = c.Route().Path
})
// Strip prefix
path := strings.TrimPrefix(c.Path(), prefix)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Add PathPrefix
if cfg.PathPrefix != "" {
// PathPrefix already has a "/" prefix
path = cfg.PathPrefix + path
}
2020-09-13 11:20:11 +02:00
var (
file http.File
stat os.FileInfo
)
if len(path) > 1 {
path = utils.TrimRight(path, '/')
}
file, err = cfg.Root.Open(path)
if err != nil && os.IsNotExist(err) && cfg.NotFoundFile != "" {
file, err = cfg.Root.Open(cfg.NotFoundFile)
}
if err != nil {
if os.IsNotExist(err) {
return c.Status(fiber.StatusNotFound).Next()
}
return
}
if stat, err = file.Stat(); err != nil {
return
}
// Serve index if path is directory
if stat.IsDir() {
indexPath := utils.TrimRight(path, '/') + cfg.Index
index, err := cfg.Root.Open(indexPath)
if err == nil {
indexStat, err := index.Stat()
if err == nil {
file = index
stat = indexStat
}
}
}
// Browse directory if no index found and browsing is enabled
if stat.IsDir() {
if cfg.Browse {
return dirList(c, file)
}
return fiber.ErrForbidden
}
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))
}
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 err
}
return nil
}
return c.Next()
2020-10-28 00:52:21 -03:00
}
}
2020-09-17 12:56:11 +08:00
2020-10-28 00:52:21 -03:00
// SendFile ...
func SendFile(c *fiber.Ctx, fs http.FileSystem, path string) (err error) {
2020-10-28 00:52:21 -03:00
var (
file http.File
stat os.FileInfo
)
2020-09-13 11:20:11 +02:00
file, err = fs.Open(path)
2020-10-28 00:52:21 -03:00
if err != nil {
if os.IsNotExist(err) {
return fiber.ErrNotFound
2020-09-13 11:20:11 +02:00
}
return err
2020-10-28 00:52:21 -03:00
}
2020-09-13 11:20:11 +02:00
2020-10-28 00:52:21 -03:00
if stat, err = file.Stat(); err != nil {
return err
2020-10-28 00:52:21 -03:00
}
// Serve index if path is directory
if stat.IsDir() {
indexPath := utils.TrimRight(path, '/') + ConfigDefault.Index
index, err := fs.Open(indexPath)
2020-10-28 00:52:21 -03:00
if err == nil {
indexStat, err := index.Stat()
2020-09-13 11:20:11 +02:00
if err == nil {
2020-10-28 00:52:21 -03:00
file = index
stat = indexStat
2020-09-13 11:20:11 +02:00
}
}
2020-10-28 00:52:21 -03:00
}
2020-09-13 11:20:11 +02:00
// Return forbidden if no index found
2020-10-28 00:52:21 -03:00
if stat.IsDir() {
return fiber.ErrForbidden
}
2020-09-13 11:20:11 +02:00
2020-10-28 00:52:21 -03:00
modTime := stat.ModTime()
contentLength := int(stat.Size())
2020-09-13 11:20:11 +02:00
2020-10-28 00:52:21 -03:00
// Set Content Type header
c.Type(getFileExtension(stat.Name()))
2020-09-13 11:20:11 +02:00
2020-10-28 00:52:21 -03:00
// Set Last Modified header
if !modTime.IsZero() {
c.Set(fiber.HeaderLastModified, modTime.UTC().Format(http.TimeFormat))
}
2020-09-13 11:20:11 +02:00
method := c.Method()
2020-10-28 00:52:21 -03:00
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 err
2020-09-13 11:20:11 +02:00
}
2020-10-28 00:52:21 -03:00
return nil
2020-09-13 11:20:11 +02:00
}
2020-10-28 00:52:21 -03:00
return nil
2020-09-13 11:20:11 +02:00
}