package http import ( "context" "time" "Moonshark/core/metadata" "Moonshark/core/routers" "Moonshark/core/runner" "Moonshark/core/sessions" "Moonshark/core/utils" "Moonshark/core/utils/config" "Moonshark/core/utils/logger" "github.com/valyala/fasthttp" ) // Server handles HTTP requests using Lua and static file routers type Server struct { luaRouter *routers.LuaRouter staticRouter *routers.StaticRouter luaRunner *runner.Runner fasthttpServer *fasthttp.Server loggingEnabled bool debugMode bool config *config.Config sessionManager *sessions.SessionManager errorConfig utils.ErrorPageConfig } // New creates a new HTTP server with optimized connection settings func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, runner *runner.Runner, loggingEnabled bool, debugMode bool, overrideDir string, config *config.Config) *Server { server := &Server{ luaRouter: luaRouter, staticRouter: staticRouter, luaRunner: runner, loggingEnabled: loggingEnabled, debugMode: debugMode, config: config, sessionManager: sessions.GlobalSessionManager, errorConfig: utils.ErrorPageConfig{ OverrideDir: overrideDir, DebugMode: debugMode, }, } // Configure fasthttp server server.fasthttpServer = &fasthttp.Server{ Handler: server.handleRequest, Name: "Moonshark/" + metadata.Version, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, MaxRequestBodySize: 16 << 20, // 16MB DisableKeepalive: false, TCPKeepalive: true, TCPKeepalivePeriod: 60 * time.Second, ReduceMemoryUsage: true, GetOnly: false, DisablePreParseMultipartForm: true, // We'll handle parsing manually } return server } // ListenAndServe starts the server on the given address func (s *Server) ListenAndServe(addr string) error { logger.ServerCont("Catch the swell at http://localhost%s", addr) return s.fasthttpServer.ListenAndServe(addr) } // Shutdown gracefully shuts down the server func (s *Server) Shutdown(ctx context.Context) error { return s.fasthttpServer.ShutdownWithContext(ctx) } // handleRequest processes the HTTP request func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { start := time.Now() method := string(ctx.Method()) path := string(ctx.Path()) // Special case for debug stats when debug mode is enabled if s.debugMode && path == "/debug/stats" { s.handleDebugStats(ctx) // Log request if s.loggingEnabled { duration := time.Since(start) LogRequest(ctx.Response.StatusCode(), method, path, duration) } return } // Process the request s.processRequest(ctx) // Log the request if s.loggingEnabled { duration := time.Since(start) LogRequest(ctx.Response.StatusCode(), method, path, duration) } } // processRequest processes the actual request func (s *Server) processRequest(ctx *fasthttp.RequestCtx) { method := string(ctx.Method()) path := string(ctx.Path()) logger.Debug("Processing request %s %s", method, path) // Try Lua routes first params := &routers.Params{} bytecode, scriptPath, found := s.luaRouter.GetBytecode(method, path, params) // Check if we found a route but it has no valid bytecode (compile error) if found && len(bytecode) == 0 { // Get the actual error from the router 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(method, path, params); node != nil && node.Error != nil { errorMsg = node.Error.Error() } logger.Error("%s %s - %s", method, path, errorMsg) // Show error page with the actual error message ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusInternalServerError) errorHTML := utils.InternalErrorPage(s.errorConfig, path, errorMsg) ctx.SetBody([]byte(errorHTML)) return } else if found { logger.Debug("Found Lua route match for %s %s with %d params", method, path, params.Count) s.handleLuaRoute(ctx, bytecode, scriptPath, params) return } // Then try static files if _, found := s.staticRouter.Match(path); found { s.staticRouter.ServeHTTP(ctx) return } // No route found - 404 Not Found ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusNotFound) ctx.SetBody([]byte(utils.NotFoundPage(s.errorConfig, path))) } // handleLuaRoute executes a Lua route func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scriptPath string, params *routers.Params) { // Create context for Lua execution luaCtx := runner.NewHTTPContext(ctx) defer luaCtx.Release() method := string(ctx.Method()) path := string(ctx.Path()) host := string(ctx.Host()) // Set up additional context values luaCtx.Set("method", method) luaCtx.Set("path", path) luaCtx.Set("host", host) // URL parameters if params.Count > 0 { paramMap := make(map[string]any, params.Count) for i, key := range params.Keys { paramMap[key] = params.Values[i] } luaCtx.Set("params", paramMap) } else { luaCtx.Set("params", make(map[string]any)) } // Parse form data for POST/PUT/PATCH requests if method == "POST" || method == "PUT" || method == "PATCH" { formData, err := ParseForm(ctx) if err == nil && len(formData) > 0 { luaCtx.Set("form", formData) } else if err != nil { logger.Warning("Error parsing form: %v", err) luaCtx.Set("form", make(map[string]any)) } else { luaCtx.Set("form", make(map[string]any)) } } else { luaCtx.Set("form", make(map[string]any)) } // Execute Lua script response, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath) if err != nil { logger.Error("Error executing Lua route: %v", err) // General error handling ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusInternalServerError) errorHTML := utils.InternalErrorPage(s.errorConfig, path, err.Error()) ctx.SetBody([]byte(errorHTML)) return } // Apply response to HTTP context runner.ApplyResponse(response, ctx) // Release the response when done runner.ReleaseResponse(response) } // handleDebugStats displays debug statistics func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) { // Collect system stats stats := utils.CollectSystemStats(s.config) // Add component stats routeCount, bytecodeBytes := s.luaRouter.GetRouteStats() //stateCount := s.luaRunner.GetStateCount() //activeStates := s.luaRunner.GetActiveStateCount() stats.Components = utils.ComponentStats{ RouteCount: routeCount, BytecodeBytes: bytecodeBytes, //StatesCount: stateCount, //ActiveStates: activeStates, } // Generate HTML page html := utils.DebugStatsPage(stats) // Send the response ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetBody([]byte(html)) }