206 lines
5.1 KiB
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")
|
|
}
|
|
}
|