157 lines
3.9 KiB
Go
157 lines
3.9 KiB
Go
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
|
|
}
|