diff --git a/.gitignore b/.gitignore index ae804f7..3080719 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,4 @@ go.work # Test directories and files config.lua -routes/ -static/ -libs/ -override/ +test/ diff --git a/Moonshark.go b/Moonshark.go index d04cea5..9095c22 100644 --- a/Moonshark.go +++ b/Moonshark.go @@ -172,12 +172,12 @@ func (s *Moonshark) initRunner() error { // Configure session cookies sessionManager.SetCookieOptions( - "MSESSID", // name - "/", // path - "", // domain - false, // secure - true, // httpOnly - 86400, // maxAge (1 day) + "MSSESSID", // name + "/", // path + "", // domain + false, // secure + true, // httpOnly + 86400, // maxAge (1 day) ) // Set up runner options diff --git a/core/config/Config.go b/core/config/Config.go index a5cb0b2..7725693 100644 --- a/core/config/Config.go +++ b/core/config/Config.go @@ -29,7 +29,6 @@ type Config struct { HTTPLoggingEnabled bool Watchers struct { Routes bool - Static bool Modules bool } @@ -64,121 +63,106 @@ func New() *Config { // Load loads configuration from a Lua file func Load(filePath string) (*Config, error) { - // For simple configs, use standard libraries to ensure proper loading + // Create Lua state state := luajit.New(true) if state == nil { return nil, errors.New("failed to create Lua state") } defer state.Close() + defer state.Cleanup() // Create config with default values config := New() // Execute the config file if err := state.DoFile(filePath); err != nil { - return nil, fmt.Errorf("%w", err) + return nil, fmt.Errorf("failed to load config file: %w", err) } - // Extract configuration values - extractConfig(state, config) + // Store values directly to the config + config.values = make(map[string]any) + + // Extract all globals individually + globalKeys := []string{ + "debug", "log_level", "port", "routes_dir", "static_dir", + "override_dir", "pool_size", "http_logging_enabled", + "watchers", "lib_dirs", + } + + for _, key := range globalKeys { + state.GetGlobal(key) + if !state.IsNil(-1) { + value, err := state.ToValue(-1) + if err == nil { + config.values[key] = value + } + } + state.Pop(1) + } + + // Apply configuration values + applyConfig(config, config.values) return config, nil } -// extractConfig extracts configuration values directly by name -func extractConfig(state *luajit.State, config *Config) { - // Process known config values directly by key - if value := extractValue(state, "debug"); value != nil { - if boolVal, ok := value.(bool); ok { - config.Debug = boolVal - } +// applyConfig applies configuration values from the globals map +func applyConfig(config *Config, globals map[string]any) { + // Server settings + if v, ok := globals["debug"].(bool); ok { + config.Debug = v + } + if v, ok := globals["log_level"].(string); ok { + config.LogLevel = v + } + if v, ok := globals["port"].(float64); ok { + config.Port = int(v) } - if value := extractValue(state, "log_level"); value != nil { - if strVal, ok := value.(string); ok { - config.LogLevel = strVal - } + // Directory paths + if v, ok := globals["routes_dir"].(string); ok { + config.RoutesDir = v + } + if v, ok := globals["static_dir"].(string); ok { + config.StaticDir = v + } + if v, ok := globals["override_dir"].(string); ok { + config.OverrideDir = v } - if value := extractValue(state, "port"); value != nil { - if numVal, ok := value.(float64); ok { - config.Port = int(numVal) - } + // Performance settings + if v, ok := globals["pool_size"].(float64); ok { + config.PoolSize = int(v) } - if value := extractValue(state, "routes_dir"); value != nil { - if strVal, ok := value.(string); ok { - config.RoutesDir = strVal - } + // Feature flags + if v, ok := globals["http_logging_enabled"].(bool); ok { + config.HTTPLoggingEnabled = v } - if value := extractValue(state, "static_dir"); value != nil { - if strVal, ok := value.(string); ok { - config.StaticDir = strVal - } - } - - if value := extractValue(state, "override_dir"); value != nil { - if strVal, ok := value.(string); ok { - config.OverrideDir = strVal - } - } - - if value := extractValue(state, "pool_size"); value != nil { - if numVal, ok := value.(float64); ok { - config.PoolSize = int(numVal) - } - } - - if value := extractValue(state, "http_logging_enabled"); value != nil { - if boolVal, ok := value.(bool); ok { - config.HTTPLoggingEnabled = boolVal + // Handle lib_dirs array more efficiently + if libDirs, ok := globals["lib_dirs"]; ok { + if dirs := extractStringArray(libDirs); len(dirs) > 0 { + config.LibDirs = dirs } } // Handle watchers table - if value := extractValue(state, "watchers"); value != nil { - if table, ok := value.(map[string]any); ok { - if routes, ok := table["routes"].(bool); ok { - config.Watchers.Routes = routes + if watchersVal, ok := globals["watchers"]; ok { + if watchers, ok := watchersVal.(map[string]any); ok { + if v, ok := watchers["routes"].(bool); ok { + config.Watchers.Routes = v } - if static, ok := table["static"].(bool); ok { - config.Watchers.Static = static + if v, ok := watchers["modules"].(bool); ok { + config.Watchers.Modules = v } - if modules, ok := table["modules"].(bool); ok { - config.Watchers.Modules = modules - } - } - } - - // Handle lib_dirs array - if value := extractValue(state, "lib_dirs"); value != nil { - if dirs := extractStringArray(value); len(dirs) > 0 { - config.LibDirs = dirs } } } -// extractValue extracts a specific global value from the Lua state -func extractValue(state *luajit.State, key string) any { - state.GetGlobal(key) - defer state.Pop(1) - - if state.IsNil(-1) { - return nil - } - - value, err := state.ToValue(-1) - if err != nil { - return nil - } - - return value -} - -// extractStringArray extracts a string array from various possible formats +// extractStringArray extracts a string array from a Lua table func extractStringArray(value any) []string { - // Check if it's a direct array + // Direct array case if arr, ok := value.([]any); ok { result := make([]string, 0, len(arr)) for _, v := range arr { @@ -189,35 +173,15 @@ func extractStringArray(value any) []string { return result } - // Check if it's in special array format (map with empty key) - valueMap, ok := value.(map[string]any) - if !ok { - return nil - } - - arr, ok := valueMap[""] - if !ok { - return nil - } - - // Handle different array types - switch arr := arr.(type) { - case []string: - return arr - case []any: - result := make([]string, 0, len(arr)) - for _, v := range arr { + // Map with numeric keys case + if tableMap, ok := value.(map[string]any); ok { + result := make([]string, 0, len(tableMap)) + for _, v := range tableMap { if str, ok := v.(string); ok { result = append(result, str) } } return result - case []float64: - result := make([]string, 0, len(arr)) - for _, v := range arr { - result = append(result, fmt.Sprintf("%g", v)) - } - return result } return nil diff --git a/core/runner/ModuleLoader.go b/core/runner/ModuleLoader.go index ba24147..0d5f9d7 100644 --- a/core/runner/ModuleLoader.go +++ b/core/runner/ModuleLoader.go @@ -486,3 +486,15 @@ func (l *ModuleLoader) ResetModules(state *luajit.State) error { end `) } + +// escapeLuaString escapes special characters in a string for Lua +func escapeLuaString(s string) string { + replacer := strings.NewReplacer( + "\\", "\\\\", + "\"", "\\\"", + "\n", "\\n", + "\r", "\\r", + "\t", "\\t", + ) + return replacer.Replace(s) +} diff --git a/core/runner/Require.go b/core/runner/Require.go deleted file mode 100644 index dbcace1..0000000 --- a/core/runner/Require.go +++ /dev/null @@ -1,388 +0,0 @@ -package runner - -import ( - "os" - "path/filepath" - "strings" - "sync" - - luajit "git.sharkk.net/Sky/LuaJIT-to-Go" -) - -// RequireConfig holds configuration for Lua's require mechanism -type RequireConfig struct { - ScriptDir string // Base directory for script being executed - LibDirs []string // Additional library directories -} - -// NativeModuleLoader uses Lua's native package.loaded as the cache -type NativeModuleLoader struct { - registry *ModuleRegistry - config *RequireConfig - mu sync.RWMutex -} - -// NewNativeModuleLoader creates a new native module loader -func NewNativeModuleLoader(config *RequireConfig) *NativeModuleLoader { - return &NativeModuleLoader{ - registry: NewModuleRegistry(), - config: config, - } -} - -// escapeLuaString escapes special characters in a string for Lua -func escapeLuaString(s string) string { - replacer := strings.NewReplacer( - "\\", "\\\\", - "\"", "\\\"", - "\n", "\\n", - "\r", "\\r", - "\t", "\\t", - ) - return replacer.Replace(s) -} - -// SetupRequire configures the require system -func (l *NativeModuleLoader) SetupRequire(state *luajit.State) error { - // Initialize our module registry in Lua - return state.DoString(` - -- Initialize global module registry - __module_paths = {} - - -- Setup fast module loading system - __module_results = {} - - -- Create module preload table - package.preload = package.preload or {} - - -- Setup module loader registry - __ready_modules = {} - `) -} - -// PreloadAllModules fully preloads modules for maximum performance -func (l *NativeModuleLoader) PreloadAllModules(state *luajit.State) error { - l.mu.Lock() - defer l.mu.Unlock() - - // Reset registry - l.registry = NewModuleRegistry() - - // Reset preloaded modules in Lua - if err := state.DoString(` - -- Reset module registry - __module_paths = {} - __module_results = {} - - -- Clear non-core modules from package.loaded - local core_modules = { - string = true, table = true, math = true, os = true, - package = true, io = true, coroutine = true, debug = true, _G = true - } - - for name in pairs(package.loaded) do - if not core_modules[name] then - package.loaded[name] = nil - end - end - - -- Reset preload table - package.preload = package.preload or {} - for name in pairs(package.preload) do - package.preload[name] = nil - end - - -- Reset ready modules - __ready_modules = {} - `); err != nil { - return err - } - - // Set up paths for require - absPaths := []string{} - pathsMap := map[string]bool{} - - // Add script directory (absolute path) - if l.config.ScriptDir != "" { - absPath, err := filepath.Abs(l.config.ScriptDir) - if err == nil && !pathsMap[absPath] { - absPaths = append(absPaths, filepath.Join(absPath, "?.lua")) - pathsMap[absPath] = true - } - } - - // Add lib directories (absolute paths) - for _, dir := range l.config.LibDirs { - if dir == "" { - continue - } - - absPath, err := filepath.Abs(dir) - if err == nil && !pathsMap[absPath] { - absPaths = append(absPaths, filepath.Join(absPath, "?.lua")) - pathsMap[absPath] = true - } - } - - // Set package.path - escapedPathStr := escapeLuaString(strings.Join(absPaths, ";")) - if err := state.DoString(`package.path = "` + escapedPathStr + `"`); err != nil { - return err - } - - // Process and preload all modules from lib directories - for _, dir := range l.config.LibDirs { - if dir == "" { - continue - } - - absDir, err := filepath.Abs(dir) - if err != nil { - continue - } - - // Find all Lua files - err = filepath.Walk(absDir, func(path string, info os.FileInfo, err error) error { - if err != nil || info.IsDir() || !strings.HasSuffix(path, ".lua") { - return nil - } - - // Get module name - relPath, err := filepath.Rel(absDir, path) - if err != nil || strings.HasPrefix(relPath, "..") { - return nil - } - - modName := strings.TrimSuffix(relPath, ".lua") - modName = strings.ReplaceAll(modName, string(filepath.Separator), ".") - - // Register module path - l.registry.Register(path, modName) - - // Register path in Lua - escapedPath := escapeLuaString(path) - escapedName := escapeLuaString(modName) - if err := state.DoString(`__module_paths["` + escapedName + `"] = "` + escapedPath + `"`); err != nil { - return nil - } - - // Compile the module - content, err := os.ReadFile(path) - if err != nil { - return nil - } - - // Precompile bytecode - bytecode, err := state.CompileBytecode(string(content), path) - if err != nil { - return nil - } - - // Load bytecode - if err := state.LoadBytecode(bytecode, path); err != nil { - return nil - } - - // Store in package.preload for fast loading - // We use string concat for efficiency (no string.format overhead) - luaCode := ` - local modname = "` + escapedName + `" - local chunk = ... - package.preload[modname] = chunk - __ready_modules[modname] = true - ` - if err := state.DoString(luaCode); err != nil { - state.Pop(1) // Remove chunk from stack - return nil - } - - state.Pop(1) // Remove chunk from stack - return nil - }) - - if err != nil { - return err - } - } - - // Install optimized require implementation - return state.DoString(` - -- Ultra-fast module loader - function __fast_require(env, modname) - -- 1. Check already loaded modules - if package.loaded[modname] then - return package.loaded[modname] - end - - -- 2. Check preloaded chunks - if __ready_modules[modname] then - local loader = package.preload[modname] - if loader then - -- Set environment - setfenv(loader, env) - - -- Execute and store result - local result = loader() - if result == nil then - result = true - end - - -- Cache in shared registry - package.loaded[modname] = result - return result - end - end - - -- 3. Direct file load as fallback - if __module_paths[modname] then - local path = __module_paths[modname] - local chunk, err = loadfile(path) - if chunk then - setfenv(chunk, env) - local result = chunk() - if result == nil then - result = true - end - package.loaded[modname] = result - return result - end - end - - -- 4. Full path search as last resort - local err_msgs = {} - for path in package.path:gmatch("[^;]+") do - local file_path = path:gsub("?", modname:gsub("%.", "/")) - local chunk, err = loadfile(file_path) - if chunk then - setfenv(chunk, env) - local result = chunk() - if result == nil then - result = true - end - package.loaded[modname] = result - return result - end - table.insert(err_msgs, "no file '" .. file_path .. "'") - end - - error("module '" .. modname .. "' not found:\n" .. table.concat(err_msgs, "\n"), 2) - end - - -- Install require factory - function __setup_require(env) - -- Create highly optimized require with closure - env.require = function(modname) - return __fast_require(env, modname) - end - return env - end - `) -} - -// NotifyFileChanged invalidates modules when files change -func (l *NativeModuleLoader) NotifyFileChanged(state *luajit.State, path string) bool { - path = filepath.Clean(path) - - // Get module name from registry - modName, found := l.registry.GetModuleName(path) - if !found { - // Try to find by path for lib dirs - for _, libDir := range l.config.LibDirs { - absDir, err := filepath.Abs(libDir) - if err != nil { - continue - } - - relPath, err := filepath.Rel(absDir, path) - if err != nil || strings.HasPrefix(relPath, "..") { - continue - } - - if strings.HasSuffix(relPath, ".lua") { - modName = strings.TrimSuffix(relPath, ".lua") - modName = strings.ReplaceAll(modName, string(filepath.Separator), ".") - found = true - break - } - } - } - - if !found { - return false - } - - // Check if this is a core module - coreName, isCoreModule := GlobalRegistry.MatchModuleName(modName) - - // Invalidate module in Lua - escapedName := escapeLuaString(modName) - invalidateCode := ` - package.loaded["` + escapedName + `"] = nil - __ready_modules["` + escapedName + `"] = nil - if package.preload then - package.preload["` + escapedName + `"] = nil - end - ` - if err := state.DoString(invalidateCode); err != nil { - return false - } - - // For core modules, reinitialize them - if isCoreModule { - if err := GlobalRegistry.InitializeModule(state, coreName); err != nil { - return false - } - return true - } - - // For regular modules, update bytecode if the file still exists - content, err := os.ReadFile(path) - if err != nil { - // File might have been deleted - just invalidate - return true - } - - // Recompile module - bytecode, err := state.CompileBytecode(string(content), path) - if err != nil { - // Invalid Lua - just invalidate - return true - } - - // Load bytecode - if err := state.LoadBytecode(bytecode, path); err != nil { - // Unable to load - just invalidate - return true - } - - // Update preload with new chunk for regular modules - luaCode := ` - -- Update module in package.preload and clear loaded - package.loaded["` + escapedName + `"] = nil - package.preload["` + escapedName + `"] = ... - __ready_modules["` + escapedName + `"] = true - ` - if err := state.DoString(luaCode); err != nil { - state.Pop(1) // Remove chunk from stack - return false - } - - state.Pop(1) // Remove chunk from stack - return true -} - -// ResetModules clears all non-core modules -func (l *NativeModuleLoader) ResetModules(state *luajit.State) error { - return state.DoString(` - local core_modules = { - string = true, table = true, math = true, os = true, - package = true, io = true, coroutine = true, debug = true, _G = true - } - - for name in pairs(package.loaded) do - if not core_modules[name] then - package.loaded[name] = nil - end - end - `) -} diff --git a/go.mod b/go.mod index 36676f4..2c7b394 100644 --- a/go.mod +++ b/go.mod @@ -2,18 +2,20 @@ module git.sharkk.net/Sky/Moonshark go 1.24.1 -require git.sharkk.net/Sky/LuaJIT-to-Go v0.0.0 +require ( + git.sharkk.net/Sky/LuaJIT-to-Go v0.0.0 + github.com/VictoriaMetrics/fastcache v1.12.2 + github.com/goccy/go-json v0.10.5 + github.com/panjf2000/ants/v2 v2.11.2 + github.com/valyala/bytebufferpool v1.0.0 + github.com/valyala/fasthttp v1.60.0 +) require ( - github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/panjf2000/ants/v2 v2.11.2 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.60.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect ) diff --git a/go.sum b/go.sum index 63ca4b9..9edc5d5 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,13 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= @@ -15,16 +17,22 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/panjf2000/ants/v2 v2.11.2 h1:AVGpMSePxUNpcLaBO34xuIgM1ZdKOiGnpxLXixLi5Jo= github.com/panjf2000/ants/v2 v2.11.2/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=