Moonshark/core/http/Server.go
2025-04-10 13:01:17 -05:00

251 lines
7.1 KiB
Go

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, method, path)
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, method string, path string) {
// Create context for Lua execution
luaCtx := runner.NewHTTPContext(ctx)
defer luaCtx.Release()
host := string(ctx.Host())
// Set up additional context values
luaCtx.Set("method", method)
luaCtx.Set("path", path)
luaCtx.Set("host", host)
// Add session data
session := s.sessionManager.GetSessionFromRequest(ctx)
luaCtx.Set("session", map[string]any{
"id": session.ID,
"data": session.Data,
})
// 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
}
// Handle persisting session data
for k, v := range response.SessionData {
session.Set(k, v)
}
s.sessionManager.ApplySessionCookie(ctx, session)
// 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))
}