package router import ( "errors" "os" "sync" "time" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" "github.com/VictoriaMetrics/fastcache" ) // Default cache sizes const ( defaultBytecodeMaxBytes = 32 * 1024 * 1024 // 32MB for bytecode cache defaultRouteMaxBytes = 8 * 1024 * 1024 // 8MB for route match cache ) // LuaRouter is a filesystem-based HTTP router for Lua files type LuaRouter struct { routesDir string // Root directory containing route files routes map[string]*node // Method -> route tree failedRoutes map[string]*RouteError // Track failed routes mu sync.RWMutex // Lock for concurrent access to routes routeCache *fastcache.Cache // Cache for route lookups bytecodeCache *fastcache.Cache // Cache for compiled bytecode // Middleware tracking for path hierarchy middlewareFiles map[string][]string // filesystem path -> middleware file paths // Caching fields middlewareCache map[string][]byte // path -> content sourceCache map[string][]byte // combined source cache key -> compiled bytecode sourceMtimes map[string]time.Time // track modification times // Shared Lua state for compilation compileState *luajit.State compileStateMu sync.Mutex // Protect concurrent access to Lua state } // NewLuaRouter creates a new LuaRouter instance func NewLuaRouter(routesDir string) (*LuaRouter, error) { info, err := os.Stat(routesDir) if err != nil { return nil, err } if !info.IsDir() { return nil, errors.New("routes path is not a directory") } // Create shared Lua state compileState := luajit.New() if compileState == nil { return nil, errors.New("failed to create Lua compile state") } r := &LuaRouter{ routesDir: routesDir, routes: make(map[string]*node), failedRoutes: make(map[string]*RouteError), middlewareFiles: make(map[string][]string), routeCache: fastcache.New(defaultRouteMaxBytes), bytecodeCache: fastcache.New(defaultBytecodeMaxBytes), middlewareCache: make(map[string][]byte), sourceCache: make(map[string][]byte), sourceMtimes: make(map[string]time.Time), compileState: compileState, } methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"} for _, method := range methods { r.routes[method] = &node{ staticChild: make(map[string]*node), } } err = r.buildRoutes() if len(r.failedRoutes) > 0 { return r, ErrRoutesCompilationErrors } return r, err } // Refresh rebuilds the router by rescanning the routes directory func (r *LuaRouter) Refresh() error { r.mu.Lock() defer r.mu.Unlock() for method := range r.routes { r.routes[method] = &node{ staticChild: make(map[string]*node), } } r.failedRoutes = make(map[string]*RouteError) r.middlewareFiles = make(map[string][]string) r.middlewareCache = make(map[string][]byte) r.sourceCache = make(map[string][]byte) r.sourceMtimes = make(map[string]time.Time) err := r.buildRoutes() if len(r.failedRoutes) > 0 { return ErrRoutesCompilationErrors } return err } // ReportFailedRoutes returns a list of routes that failed to compile func (r *LuaRouter) ReportFailedRoutes() []*RouteError { r.mu.RLock() defer r.mu.RUnlock() result := make([]*RouteError, 0, len(r.failedRoutes)) for _, re := range r.failedRoutes { result = append(result, re) } return result } // Close cleans up the router and its resources func (r *LuaRouter) Close() { r.compileStateMu.Lock() if r.compileState != nil { r.compileState.Close() r.compileState = nil } r.compileStateMu.Unlock() } // GetRouteStats returns statistics about the router func (r *LuaRouter) GetRouteStats() (int, int64) { r.mu.RLock() defer r.mu.RUnlock() routeCount := 0 bytecodeBytes := int64(0) for _, root := range r.routes { count, bytes := countNodesAndBytecode(root) routeCount += count bytecodeBytes += bytes } return routeCount, bytecodeBytes } type NodeWithError struct { ScriptPath string Error error }