// server.go - Simplified HTTP server package http import ( "fmt" "strings" "time" "Moonshark/config" "Moonshark/metadata" "Moonshark/runner" "Moonshark/utils" "github.com/goccy/go-json" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" ) 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 Send404(ctx *fasthttp.RequestCtx) { ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusNotFound) ctx.SetBody([]byte(utils.NotFoundPage(ctx.URI().String()))) } func Send500(ctx *fasthttp.RequestCtx, err error) { ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusInternalServerError) if err == nil { ctx.SetBody([]byte(utils.InternalErrorPage(string(ctx.Path()), ""))) } else { ctx.SetBody([]byte(utils.InternalErrorPage(string(ctx.Path()), err.Error()))) } } // ApplyResponse applies a Response to a fasthttp.RequestCtx func ApplyResponse(resp *runner.Response, ctx *fasthttp.RequestCtx) { // Set status code ctx.SetStatusCode(resp.Status) // Set headers for name, value := range resp.Headers { ctx.Response.Header.Set(name, value) } // Set cookies for _, cookie := range resp.Cookies { ctx.Response.Header.SetCookie(cookie) } // Process the body based on its type if resp.Body == nil { return } // Check if Content-Type was manually set contentTypeSet := false for name := range resp.Headers { if strings.ToLower(name) == "content-type" { contentTypeSet = true break } } // Get a buffer from the pool buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) // Set body based on type switch body := resp.Body.(type) { case string: if !contentTypeSet { ctx.Response.Header.SetContentType("text/plain; charset=utf-8") } ctx.SetBodyString(body) case []byte: if !contentTypeSet { ctx.Response.Header.SetContentType("text/plain; charset=utf-8") } ctx.SetBody(body) case map[string]any, map[any]any, []any, []float64, []string, []int, []map[string]any: // Marshal JSON if err := json.NewEncoder(buf).Encode(body); err == nil { if !contentTypeSet { ctx.Response.Header.SetContentType("application/json") } ctx.SetBody(buf.Bytes()) } else { // Fallback to string representation if !contentTypeSet { ctx.Response.Header.SetContentType("text/plain; charset=utf-8") } ctx.SetBodyString(fmt.Sprintf("%v", body)) } default: // Check if it's any other map or slice type typeStr := fmt.Sprintf("%T", body) if typeStr[0] == '[' || (len(typeStr) > 3 && typeStr[:3] == "map") { if err := json.NewEncoder(buf).Encode(body); err == nil { if !contentTypeSet { ctx.Response.Header.SetContentType("application/json") } ctx.SetBody(buf.Bytes()) } else { if !contentTypeSet { ctx.Response.Header.SetContentType("text/plain; charset=utf-8") } ctx.SetBodyString(fmt.Sprintf("%v", body)) } } else { // Default to string representation if !contentTypeSet { ctx.Response.Header.SetContentType("text/plain; charset=utf-8") } ctx.SetBodyString(fmt.Sprintf("%v", body)) } } } /* func 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))) } */