// server.go - Simplified HTTP server package http import ( "context" "strings" "time" "Moonshark/config" "Moonshark/logger" "Moonshark/metadata" "Moonshark/runner" "Moonshark/sessions" "Moonshark/utils" "git.sharkk.net/Go/Color" "github.com/valyala/fasthttp" ) var emptyMap = make(map[string]any) func NewHttpServer(cfg *config.Config, handler fasthttp.RequestHandler, dbg bool) *fasthttp.Server { return &fasthttp.Server{ Handler: handler, Name: "Moonshark/" + metadata.Version, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, MaxRequestBodySize: 16 << 20, TCPKeepalive: true, ReduceMemoryUsage: true, StreamRequestBody: true, NoDefaultServerHeader: true, } } func NewPublicHandler(pubDir, prefix string) fasthttp.RequestHandler { if !strings.HasPrefix(prefix, "/") { prefix = "/" + prefix } if !strings.HasSuffix(prefix, "/") { prefix += "/" } fs := &fasthttp.FS{ Root: pubDir, IndexNames: []string{"index.html"}, AcceptByteRange: true, Compress: true, CompressedFileSuffix: ".gz", CompressBrotli: true, PathRewrite: fasthttp.NewPathPrefixStripper(len(prefix) - 1), } return fs.NewRequestHandler() } func (s *Server) ListenAndServe(addr string) error { logger.Infof("Catch the swell at %s", color.Cyan("http://localhost"+addr)) return s.fasthttpServer.ListenAndServe(addr) } func (s *Server) Shutdown(ctx context.Context) error { return s.fasthttpServer.ShutdownWithContext(ctx) } func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { start := time.Now() method := string(ctx.Method()) path := string(ctx.Path()) // Route lookup bytecode, params, found := s.luaRouter.Lookup(method, path) if !found { s.send404(ctx) s.logRequest(ctx, method, path, time.Since(start)) return } if len(bytecode) == 0 { s.send500(ctx, nil) s.logRequest(ctx, method, path, time.Since(start)) return } // Get session session := s.sessionManager.GetSessionFromRequest(ctx) // Execute Lua script response, err := s.luaRunner.ExecuteHTTP(bytecode, ctx, params, session) if err != nil { logger.Errorf("Lua execution error: %v", err) s.send500(ctx, err) s.logRequest(ctx, method, path, time.Since(start)) return } // Apply response s.applyResponse(ctx, response, session) runner.ReleaseResponse(response) s.logRequest(ctx, method, path, time.Since(start)) } func (s *Server) applyResponse(ctx *fasthttp.RequestCtx, resp *runner.Response, session *sessions.Session) { // Handle session updates if len(resp.SessionData) > 0 { if _, clearAll := resp.SessionData["__clear_all"]; clearAll { session.Clear() session.ClearFlash() delete(resp.SessionData, "__clear_all") } for k, v := range resp.SessionData { if v == "__DELETE__" { session.Delete(k) } else { session.Set(k, v) } } } // Handle flash data if flashData, ok := resp.Metadata["flash"].(map[string]any); ok { for k, v := range flashData { if err := session.FlashSafe(k, v); err != nil && s.debugMode { logger.Warnf("Error setting flash data %s: %v", k, err) } } } // Apply session cookie s.sessionManager.ApplySessionCookie(ctx, session) // Apply HTTP response runner.ApplyResponse(resp, ctx) } func (s *Server) send404(ctx *fasthttp.RequestCtx) { ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusNotFound) cacheMu.RLock() ctx.SetBody(cached404) cacheMu.RUnlock() } func (s *Server) send500(ctx *fasthttp.RequestCtx, err error) { ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusInternalServerError) if err == nil { cacheMu.RLock() ctx.SetBody(cached500) cacheMu.RUnlock() } else { errorConfig := utils.ErrorPageConfig{ OverrideDir: s.cfg.Dirs.Override, DebugMode: s.debugMode, } ctx.SetBody([]byte(utils.InternalErrorPage(errorConfig, string(ctx.Path()), err.Error()))) } } func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) { stats := utils.CollectSystemStats(s.cfg) stats.Components = utils.ComponentStats{ RouteCount: 0, // TODO: Get from router BytecodeBytes: 0, // TODO: Get from router SessionStats: s.sessionManager.GetCacheStats(), } ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetBody([]byte(utils.DebugStatsPage(stats))) } func (s *Server) logRequest(ctx *fasthttp.RequestCtx, method, path string, duration time.Duration) { if s.cfg.Server.HTTPLogging { logger.Request(ctx.Response.StatusCode(), method, path, duration) } }