error page upgrades

This commit is contained in:
Sky Johnson 2025-03-25 21:19:45 -05:00
parent be57476a8e
commit 5ab4fd7456
5 changed files with 155 additions and 22 deletions

View File

@ -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

26
core/routers/errors.go Normal file
View File

@ -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()
}

View File

@ -17,6 +17,7 @@ const maxParams = 20
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
}
@ -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
@ -60,6 +62,7 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) {
r := &LuaRouter{
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
}

View File

@ -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

View File

@ -2,6 +2,7 @@ package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
@ -37,8 +38,22 @@ func initRouters(routesDir, staticDir string, log *logger.Logger) (*routers.LuaR
// Initialize Lua router for dynamic routes
luaRouter, err := routers.NewLuaRouter(routesDir)
if err != nil {
// 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)
// Initialize static file router