mirror of https://github.com/gofiber/fiber.git
178 lines
4.2 KiB
Go
178 lines
4.2 KiB
Go
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,
|
|
CompressBrotli: config.Compress, // Brotli compression won't work without this
|
|
CompressedFileSuffixes: c.App().Config().CompressedFileSuffixes,
|
|
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.RequestCtx())
|
|
|
|
// 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.RequestCtx().Response.StatusCode()
|
|
|
|
if status != fiber.StatusNotFound && status != fiber.StatusForbidden {
|
|
if len(cacheControlValue) > 0 {
|
|
c.RequestCtx().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.RequestCtx().SetContentType("") // Issue #420
|
|
c.RequestCtx().Response.SetStatusCode(fiber.StatusOK)
|
|
c.RequestCtx().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
|
|
}
|