From 5ab4fd74569ac740b4d210572f033e432718f2ec Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Tue, 25 Mar 2025 21:19:45 -0500 Subject: [PATCH] error page upgrades --- core/http/server.go | 23 ++++++++- core/routers/errors.go | 26 ++++++++++ core/routers/luarouter.go | 105 ++++++++++++++++++++++++++++++++------ core/utils/errorpages.go | 6 +-- moonshark.go | 17 +++++- 5 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 core/routers/errors.go diff --git a/core/http/server.go b/core/http/server.go index 9580d44..2681a2b 100644 --- a/core/http/server.go +++ b/core/http/server.go @@ -98,7 +98,28 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) { // Try Lua routes first params := &routers.Params{} - if bytecode, scriptPath, found := s.luaRouter.GetBytecode(r.Method, r.URL.Path, params); found { + bytecode, scriptPath, found := s.luaRouter.GetBytecode(r.Method, r.URL.Path, params) + + // Check if we found a route but it has no valid bytecode (compile error) + if found && (bytecode == nil || len(bytecode) == 0) { + // Get the actual error from the router - this requires exposing the actual error + // from the node in the GetBytecode method + errorMsg := "Route exists but failed to compile. Check server logs for details." + + // Get the actual node to access its error + if node, _ := s.luaRouter.GetNodeWithError(r.Method, r.URL.Path, params); node != nil && node.Error != nil { + errorMsg = node.Error.Error() + } + + s.logger.Error("%s %s - %s", r.Method, r.URL.Path, errorMsg) + + // Show error page with the actual error message + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + errorHTML := utils.InternalErrorPage(s.errorConfig, r.URL.Path, errorMsg) + w.Write([]byte(errorHTML)) + return + } else if found { s.logger.Debug("Found Lua route match for %s %s with %d params", r.Method, r.URL.Path, params.Count) s.handleLuaRoute(w, r, bytecode, scriptPath, params) return diff --git a/core/routers/errors.go b/core/routers/errors.go new file mode 100644 index 0000000..24175cb --- /dev/null +++ b/core/routers/errors.go @@ -0,0 +1,26 @@ +package routers + +import "errors" + +// Common errors +var ( + // ErrRoutesCompilationErrors indicates that some routes failed to compile + // but the router is still operational + ErrRoutesCompilationErrors = errors.New("some routes failed to compile") +) + +// RouteError represents an error with a specific route +type RouteError struct { + Path string // The URL path + Method string // HTTP method + ScriptPath string // Path to the Lua script + Err error // The actual error +} + +// Error returns the error message +func (re *RouteError) Error() string { + if re.Err == nil { + return "unknown route error" + } + return re.Err.Error() +} diff --git a/core/routers/luarouter.go b/core/routers/luarouter.go index e660159..18b6288 100644 --- a/core/routers/luarouter.go +++ b/core/routers/luarouter.go @@ -15,9 +15,10 @@ const maxParams = 20 // 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 - mu sync.RWMutex // Lock for concurrent access to routes + 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 } // node represents a node in the routing trie @@ -27,6 +28,7 @@ type node struct { paramName string // Parameter name (if this is a parameter node) staticChild map[string]*node // Static children by segment name paramChild *node // Parameter/wildcard child + err error // Compilation error if any } // Params holds URL parameters with fixed-size arrays to avoid allocations @@ -58,8 +60,9 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) { } r := &LuaRouter{ - routesDir: routesDir, - routes: make(map[string]*node), + routesDir: routesDir, + routes: make(map[string]*node), + failedRoutes: make(map[string]*RouteError), } // Initialize method trees @@ -71,15 +74,22 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) { } // Build routes - if err := r.buildRoutes(); err != nil { - return nil, err + err = r.buildRoutes() + + // If some routes failed to compile, return the router with a warning error + // This allows the server to continue running with the routes that did compile + if len(r.failedRoutes) > 0 { + return r, ErrRoutesCompilationErrors } - return r, nil + return r, err } // buildRoutes scans the routes directory and builds the routing tree func (r *LuaRouter) buildRoutes() error { + // Clear failed routes map + r.failedRoutes = make(map[string]*RouteError) + return filepath.Walk(r.routesDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -116,8 +126,9 @@ func (r *LuaRouter) buildRoutes() error { urlPath = "/" + strings.ReplaceAll(relDir, "\\", "/") } - // Add route to tree - return r.addRoute(root, urlPath, path) + // Add route to tree - continue even if there are errors + r.addRoute(root, urlPath, path) + return nil }) } @@ -152,13 +163,24 @@ func (r *LuaRouter) addRoute(root *node, urlPath, handlerPath string) error { current.handler = handlerPath // Compile Lua file to bytecode - if err := r.compileHandler(current); err != nil { - return err + if err := r.compileHandler(current, urlPath); err != nil { + // Track the failure but don't fail the entire process + routeKey := getRouteKey(urlPath, handlerPath) + r.failedRoutes[routeKey] = &RouteError{ + Path: urlPath, + ScriptPath: handlerPath, + Err: err, + } } return nil } +// getRouteKey generates a unique key for a route +func getRouteKey(path, handler string) string { + return path + ":" + handler +} + // Match finds a handler for the given method and path // Uses the pre-allocated params struct to avoid allocations func (r *LuaRouter) Match(method, path string, params *Params) (*node, bool) { @@ -222,7 +244,7 @@ func (r *LuaRouter) matchPath(current *node, segments []string, params *Params, } // compileHandler compiles a Lua file to bytecode -func (r *LuaRouter) compileHandler(n *node) error { +func (r *LuaRouter) compileHandler(n *node, urlPath string) error { if n.handler == "" { return nil } @@ -230,32 +252,44 @@ func (r *LuaRouter) compileHandler(n *node) error { // Read the Lua file content, err := os.ReadFile(n.handler) if err != nil { + n.err = err // Store the error in the node return err } // Compile to bytecode state := luajit.New() if state == nil { - return errors.New("failed to create Lua state") + compileErr := errors.New("failed to create Lua state") + n.err = compileErr // Store the error in the node + return compileErr } defer state.Close() bytecode, err := state.CompileBytecode(string(content), n.handler) if err != nil { + n.err = err // Store the error in the node return err } // Store bytecode in the node n.bytecode = bytecode + n.err = nil // Clear any previous error return nil } // GetBytecode returns the compiled bytecode for a matched route +// If a route exists but failed to compile, returns nil bytecode with found=true func (r *LuaRouter) GetBytecode(method, path string, params *Params) ([]byte, string, bool) { node, found := r.Match(method, path, params) if !found { return nil, "", false } + + // If the route exists but has a compilation error + if node.err != nil { + return nil, node.handler, true + } + return node.bytecode, node.handler, true } @@ -271,6 +305,47 @@ func (r *LuaRouter) Refresh() error { } } + // Clear failed routes + r.failedRoutes = make(map[string]*RouteError) + // Rebuild routes - return r.buildRoutes() + err := r.buildRoutes() + + // If some routes failed to compile, return a warning error + 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 +} + +type NodeWithError struct { + ScriptPath string + Error error +} + +// GetNodeWithError returns the node with its error for a given path +func (r *LuaRouter) GetNodeWithError(method, path string, params *Params) (*NodeWithError, bool) { + node, found := r.Match(method, path, params) + if !found { + return nil, false + } + + return &NodeWithError{ + ScriptPath: node.handler, + Error: node.err, + }, true } diff --git a/core/utils/errorpages.go b/core/utils/errorpages.go index 40fa5ae..dafe216 100644 --- a/core/utils/errorpages.go +++ b/core/utils/errorpages.go @@ -89,11 +89,7 @@ func generateInternalErrorHTML(debugMode bool, url string, errMsg string) string } randomMessage := errorMessages[rand.Intn(len(errorMessages))] - codeContent := url - if errMsg != "" { - codeContent = url + " → " + errMsg - } - return generateErrorHTML("500", randomMessage, "Internal Server Error", debugMode, codeContent) + return generateErrorHTML("500", randomMessage, "Internal Server Error", debugMode, errMsg) } // generateNotFoundHTML creates a 404 Not Found error page diff --git a/moonshark.go b/moonshark.go index 78379e1..a8072a0 100644 --- a/moonshark.go +++ b/moonshark.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" "os" "os/signal" @@ -37,7 +38,21 @@ func initRouters(routesDir, staticDir string, log *logger.Logger) (*routers.LuaR // Initialize Lua router for dynamic routes luaRouter, err := routers.NewLuaRouter(routesDir) if err != nil { - return nil, nil, fmt.Errorf("failed to initialize Lua router: %v", err) + // Check if this is a compilation warning or a more serious error + if errors.Is(err, routers.ErrRoutesCompilationErrors) { + // Some routes failed to compile, but router is still usable + log.Warning("Some Lua routes failed to compile. Check logs for details.") + + // Log details about each failed route + if failedRoutes := luaRouter.ReportFailedRoutes(); len(failedRoutes) > 0 { + for _, re := range failedRoutes { + log.Error("Route %s %s failed to compile: %v", re.Method, re.Path, re.Err) + } + } + } else { + // More serious error that prevents router initialization + return nil, nil, fmt.Errorf("failed to initialize Lua router: %v", err) + } } log.Info("Lua router initialized with routes from %s", routesDir)