Moonshark/core/routers/StaticRouter.go

206 lines
5.1 KiB
Go

package routers
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"Moonshark/core/utils/logger"
"github.com/valyala/fasthttp"
)
// StaticRouter is a simplified router for static files using FastHTTP's built-in capabilities
type StaticRouter struct {
fs *fasthttp.FS
fsHandler fasthttp.RequestHandler
urlPrefix string
rootDir string
log bool
// Additional compression options
useBrotli bool
useZstd bool
}
// NewStaticRouter creates a new StaticRouter instance
func NewStaticRouter(rootDir string) (*StaticRouter, error) {
// Verify root directory exists
info, err := os.Stat(rootDir)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, errors.New("root path is not a directory")
}
// Create the FS handler with optimized settings
fs := &fasthttp.FS{
Root: rootDir,
IndexNames: []string{"index.html"},
GenerateIndexPages: false,
AcceptByteRange: true,
Compress: true,
CacheDuration: 24 * time.Hour,
CompressedFileSuffix: ".gz",
CompressBrotli: true,
CompressZstd: true,
CompressedFileSuffixes: map[string]string{
"gzip": ".gz",
"br": ".br",
"zstd": ".zst",
},
}
r := &StaticRouter{
fs: fs,
urlPrefix: "/static", // Default prefix
rootDir: rootDir,
log: false,
useBrotli: true,
useZstd: true,
}
// Set up the path rewrite based on the prefix
r.updatePathRewrite()
return r, nil
}
// WithEmbeddedFS sets an embedded filesystem instead of using the rootDir
func (r *StaticRouter) WithEmbeddedFS(embedded fs.FS) *StaticRouter {
r.fs.FS = embedded
r.fsHandler = r.fs.NewRequestHandler()
return r
}
// SetCompression configures the compression options
func (r *StaticRouter) SetCompression(useGzip, useBrotli, useZstd bool) {
r.fs.Compress = useGzip
r.fs.CompressBrotli = useBrotli
r.fs.CompressZstd = useZstd
r.useBrotli = useBrotli
r.useZstd = useZstd
// Update handler to reflect changes
r.fsHandler = r.fs.NewRequestHandler()
}
// SetCacheDuration sets the cache duration for HTTP headers
func (r *StaticRouter) SetCacheDuration(duration time.Duration) {
r.fs.CacheDuration = duration
r.fsHandler = r.fs.NewRequestHandler()
}
// SetURLPrefix sets the URL prefix for static assets
func (r *StaticRouter) SetURLPrefix(prefix string) {
if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
r.urlPrefix = prefix
r.updatePathRewrite()
}
// updatePathRewrite updates the path rewriter based on the current prefix
func (r *StaticRouter) updatePathRewrite() {
r.fs.PathRewrite = fasthttp.NewPathPrefixStripper(len(r.urlPrefix))
r.fsHandler = r.fs.NewRequestHandler()
}
// EnableDebugLog enables debug logging
func (r *StaticRouter) EnableDebugLog() {
r.log = true
}
// DisableDebugLog disables debug logging
func (r *StaticRouter) DisableDebugLog() {
r.log = false
}
// ServeHTTP implements the http.Handler interface through fasthttpadaptor
func (r *StaticRouter) ServeHTTP(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
// Check if path starts with the prefix
if !strings.HasPrefix(path, r.urlPrefix) {
ctx.NotFound()
return
}
if r.log {
logger.Debug("[StaticRouter] Serving: %s", path)
}
// Handle the request with the FS handler
r.fsHandler(ctx)
}
// Match finds a file path for the given URL path
func (r *StaticRouter) Match(urlPath string) (string, bool) {
// Check if path starts with the prefix
if !strings.HasPrefix(urlPath, r.urlPrefix) {
return "", false
}
// Strip prefix
urlPath = strings.TrimPrefix(urlPath, r.urlPrefix)
// Make sure path starts with /
if !strings.HasPrefix(urlPath, "/") {
urlPath = "/" + urlPath
}
filePath := filepath.Join(r.rootDir, filepath.FromSlash(strings.TrimPrefix(urlPath, "/")))
_, err := os.Stat(filePath)
if r.log && err == nil {
logger.Debug("[StaticRouter] MATCH: %s -> %s", urlPath, filePath)
}
return filePath, err == nil
}
// Refresh is a no-op in this implementation as there's no cache to refresh
func (r *StaticRouter) Refresh() error {
// No cache to refresh in this implementation
return nil
}
// GetStats returns basic stats about the router
func (r *StaticRouter) GetStats() map[string]any {
return map[string]any{
"type": "StaticRouter",
"rootDir": r.rootDir,
"urlPrefix": r.urlPrefix,
"useBrotli": r.useBrotli,
"useZstd": r.useZstd,
"cacheTime": r.fs.CacheDuration.String(),
}
}
// SetMaxItems is kept for API compatibility with the old router
func (r *StaticRouter) SetMaxItems(n int) {
// No-op in this implementation
}
// SetMaxItemSize is kept for API compatibility with the old router
func (r *StaticRouter) SetMaxItemSize(n int) {
// No-op in this implementation
}
// SetTotalCapacity is kept for API compatibility with the old router
func (r *StaticRouter) SetTotalCapacity(n int) {
// No-op in this implementation
}
// PreloadCommonFiles is kept for API compatibility but is a no-op
// as FastHTTP doesn't have built-in preloading
func (r *StaticRouter) PreloadCommonFiles() {
// No preloading in this implementation
if r.log {
logger.Debug("[StaticRouter] PreloadCommonFiles is a no-op in StaticRouter")
}
}