package routers import ( "errors" "os" "path/filepath" "strings" "sync" ) // StaticRouter is a filesystem-based router for static files type StaticRouter struct { rootDir string // Root directory containing files routes map[string]string // Direct mapping from URL path to file path mu sync.RWMutex // Lock for concurrent access to routes } // 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") } r := &StaticRouter{ rootDir: rootDir, routes: make(map[string]string), } // Build routes if err := r.buildRoutes(); err != nil { return nil, err } return r, nil } // buildRoutes scans the root directory and builds the routing map func (r *StaticRouter) buildRoutes() error { return filepath.Walk(r.rootDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Skip directories if info.IsDir() { return nil } // Get relative path for URL relPath, err := filepath.Rel(r.rootDir, path) if err != nil { return err } // Convert to URL path with forward slashes for consistency urlPath := "/" + strings.ReplaceAll(relPath, "\\", "/") // Add to routes map r.routes[urlPath] = path return nil }) } // Match finds a file path for the given URL path func (r *StaticRouter) Match(path string) (string, bool) { r.mu.RLock() defer r.mu.RUnlock() filePath, found := r.routes[path] return filePath, found } // Refresh rebuilds the router by rescanning the root directory func (r *StaticRouter) Refresh() error { r.mu.Lock() defer r.mu.Unlock() // Clear routes r.routes = make(map[string]string) // Rebuild routes return r.buildRoutes() }