diff --git a/core/http/Csrf.go b/core/http/Csrf.go index 6a986a6..5317959 100644 --- a/core/http/Csrf.go +++ b/core/http/Csrf.go @@ -1,20 +1,22 @@ package http import ( - "net/http" - "git.sharkk.net/Sky/Moonshark/core/logger" "git.sharkk.net/Sky/Moonshark/core/utils" + "github.com/valyala/fasthttp" ) // HandleCSRFError handles a CSRF validation error -func HandleCSRFError(w http.ResponseWriter, r *http.Request, errorConfig utils.ErrorPageConfig) { - logger.Warning("CSRF validation failed for %s %s", r.Method, r.URL.Path) +func HandleCSRFError(ctx *fasthttp.RequestCtx, errorConfig utils.ErrorPageConfig) { + method := string(ctx.Method()) + path := string(ctx.Path()) - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusForbidden) + logger.Warning("CSRF validation failed for %s %s", method, path) + + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(fasthttp.StatusForbidden) errorMsg := "Invalid or missing CSRF token. This could be due to an expired form or a cross-site request forgery attempt." - errorHTML := utils.ForbiddenPage(errorConfig, r.URL.Path, errorMsg) - w.Write([]byte(errorHTML)) + errorHTML := utils.ForbiddenPage(errorConfig, path, errorMsg) + ctx.SetBody([]byte(errorHTML)) } diff --git a/core/http/Forms.go b/core/http/Forms.go index b68d4e6..9c2720b 100644 --- a/core/http/Forms.go +++ b/core/http/Forms.go @@ -2,11 +2,10 @@ package http import ( "errors" - "io" - "mime" - "net/http" - "net/url" + "mime/multipart" "strings" + + "github.com/valyala/fasthttp" ) // Maximum form parse size (16MB) @@ -20,113 +19,94 @@ var ( // ParseForm parses a POST request body into a map of values // Supports both application/x-www-form-urlencoded and multipart/form-data content types -func ParseForm(r *http.Request) (map[string]any, error) { +func ParseForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { // Only handle POST, PUT, PATCH - if r.Method != http.MethodPost && - r.Method != http.MethodPut && - r.Method != http.MethodPatch { + method := string(ctx.Method()) + if method != "POST" && method != "PUT" && method != "PATCH" { return make(map[string]any), nil } // Check content type - contentType := r.Header.Get("Content-Type") + contentType := string(ctx.Request.Header.ContentType()) if contentType == "" { return make(map[string]any), nil } - // Parse the media type - mediaType, params, err := mime.ParseMediaType(contentType) - if err != nil { - return nil, ErrInvalidFormType - } - result := make(map[string]any) - switch { - case mediaType == "application/x-www-form-urlencoded": - // Handle URL-encoded form - if err := parseURLEncodedForm(r, result); err != nil { - return nil, err - } - - case strings.HasPrefix(mediaType, "multipart/form-data"): - // Handle multipart form - boundary := params["boundary"] - if boundary == "" { - return nil, ErrInvalidFormType - } - - if err := parseMultipartForm(r, boundary, result); err != nil { - return nil, err - } - - default: - // Unrecognized content type - return make(map[string]any), nil + // Check for content length to prevent DOS + if len(ctx.Request.Body()) > maxFormSize { + return nil, ErrFormSizeTooLarge } + // Handle by content type + if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") { + return parseURLEncodedForm(ctx) + } else if strings.HasPrefix(contentType, "multipart/form-data") { + return parseMultipartForm(ctx) + } + + // Unrecognized content type return result, nil } // parseURLEncodedForm handles application/x-www-form-urlencoded forms -func parseURLEncodedForm(r *http.Request, result map[string]any) error { - // Enforce size limit - r.Body = http.MaxBytesReader(nil, r.Body, maxFormSize) +func parseURLEncodedForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { + result := make(map[string]any) - // Read the entire body - body, err := io.ReadAll(r.Body) - if err != nil { - if strings.Contains(err.Error(), "http: request body too large") { - return ErrFormSizeTooLarge + // Process form values directly from PostArgs() + ctx.PostArgs().VisitAll(func(key, value []byte) { + keyStr := string(key) + valStr := string(value) + + // Check if we already have this key + if existing, ok := result[keyStr]; ok { + // If it's already a slice, append + if existingSlice, ok := existing.([]string); ok { + result[keyStr] = append(existingSlice, valStr) + } else { + // Convert to slice and append + result[keyStr] = []string{existing.(string), valStr} + } + } else { + // New key + result[keyStr] = valStr } - return err - } + }) - // Parse form values - form, err := url.ParseQuery(string(body)) - if err != nil { - return err - } - - // Convert to map[string]any - for key, values := range form { - if len(values) == 1 { - // Single value - result[key] = values[0] - } else if len(values) > 1 { - // Multiple values - result[key] = values - } - } - - return nil + return result, nil } // parseMultipartForm handles multipart/form-data forms -func parseMultipartForm(r *http.Request, boundary string, result map[string]any) error { - // Limit the form size - if err := r.ParseMultipartForm(maxFormSize); err != nil { - if strings.Contains(err.Error(), "http: request body too large") { - return ErrFormSizeTooLarge +func parseMultipartForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { + result := make(map[string]any) + + // Parse multipart form + form, err := ctx.MultipartForm() + if err != nil { + if err == multipart.ErrMessageTooLarge || strings.Contains(err.Error(), "too large") { + return nil, ErrFormSizeTooLarge } - return err + return nil, err } // Process form values - for key, values := range r.MultipartForm.Value { + for key, values := range form.Value { if len(values) == 1 { // Single value result[key] = values[0] } else if len(values) > 1 { - // Multiple values - result[key] = values + // Multiple values - store as string slice + strValues := make([]string, len(values)) + copy(strValues, values) + result[key] = strValues } } // We don't handle file uploads here - could be extended in the future // if needed to support file uploads to Lua - return nil + return result, nil } // Usage: diff --git a/core/http/HttpLogger.go b/core/http/HttpLogger.go index 6fee2b4..aea4a1d 100644 --- a/core/http/HttpLogger.go +++ b/core/http/HttpLogger.go @@ -1,7 +1,6 @@ package http import ( - "net/http" "time" "git.sharkk.net/Sky/Moonshark/core/logger" @@ -18,13 +17,13 @@ const ( ) // LogRequest logs an HTTP request with custom formatting -func LogRequest(statusCode int, r *http.Request, duration time.Duration) { +func LogRequest(statusCode int, method, path string, duration time.Duration) { statusColor := getStatusColor(statusCode) // Use the logger's raw message writer to bypass the standard format logger.LogRaw("%s%s%s %s%d %s%s %s %s(%v)%s", colorGray, time.Now().Format(logger.TimeFormat()), colorReset, - statusColor, statusCode, r.Method, colorReset, r.URL.Path, colorGray, duration, colorReset) + statusColor, statusCode, method, colorReset, path, colorGray, duration, colorReset) } // getStatusColor returns the ANSI color code for a status code diff --git a/core/http/Queries.go b/core/http/Queries.go index 46f8ff6..d972138 100644 --- a/core/http/Queries.go +++ b/core/http/Queries.go @@ -1,36 +1,42 @@ package http -import "net/http" +import ( + "github.com/valyala/fasthttp" +) // QueryToLua converts HTTP query parameters to a map that can be used with LuaJIT. // Single value parameters are stored as strings. // Multi-value parameters are converted to []any arrays. -func QueryToLua(r *http.Request) map[string]any { - if r == nil || r.URL == nil { - return nil - } +func QueryToLua(ctx *fasthttp.RequestCtx) map[string]any { + result := make(map[string]any) - query := r.URL.Query() - if len(query) == 0 { - return nil // Avoid allocation for empty queries - } + // Use a map to track keys that have multiple values + multiValueKeys := make(map[string]bool) - result := make(map[string]any, len(query)) - for key, values := range query { - switch len(values) { - case 0: - // Skip empty values - case 1: - // Single value - result[key] = values[0] - default: - // Multiple values - convert to []any - arr := make([]any, len(values)) - for i, v := range values { - arr[i] = v + // Process all query args + ctx.QueryArgs().VisitAll(func(key, value []byte) { + keyStr := string(key) + valStr := string(value) + + if _, exists := result[keyStr]; exists { + // This key already exists, convert to array if not already + if !multiValueKeys[keyStr] { + // First duplicate, convert existing value to array + multiValueKeys[keyStr] = true + result[keyStr] = []any{result[keyStr], valStr} + } else { + // Already an array, append + result[keyStr] = append(result[keyStr].([]any), valStr) } - result[key] = arr + } else { + // New key + result[keyStr] = valStr } + }) + + // If we don't have any query parameters, return empty map + if len(result) == 0 { + return make(map[string]any) } return result diff --git a/core/http/Server.go b/core/http/Server.go index 170bc09..e49af24 100644 --- a/core/http/Server.go +++ b/core/http/Server.go @@ -3,9 +3,7 @@ package http import ( "context" "encoding/json" - "fmt" // Added for fmt.Fprintf - "net" - "net/http" + "fmt" "time" "git.sharkk.net/Sky/Moonshark/core/config" @@ -13,6 +11,8 @@ import ( "git.sharkk.net/Sky/Moonshark/core/routers" "git.sharkk.net/Sky/Moonshark/core/runner" "git.sharkk.net/Sky/Moonshark/core/utils" + + "github.com/valyala/fasthttp" ) // Server handles HTTP requests using Lua and static file routers @@ -20,9 +20,9 @@ type Server struct { luaRouter *routers.LuaRouter staticRouter *routers.StaticRouter luaRunner *runner.Runner - httpServer *http.Server + fasthttpServer *fasthttp.Server loggingEnabled bool - debugMode bool // Controls whether to show error details + debugMode bool config *config.Config errorConfig utils.ErrorPageConfig } @@ -35,7 +35,6 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, runne luaRouter: luaRouter, staticRouter: staticRouter, luaRunner: runner, - httpServer: &http.Server{}, loggingEnabled: loggingEnabled, debugMode: debugMode, config: config, @@ -44,15 +43,20 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, runne DebugMode: debugMode, }, } - server.httpServer.Handler = server - // Set TCP keep-alive for connections - server.httpServer.ConnState = func(conn net.Conn, state http.ConnState) { - if state == http.StateNew { - if tcpConn, ok := conn.(*net.TCPConn); ok { - tcpConn.SetKeepAlive(true) - } - } + // Configure fasthttp server + server.fasthttpServer = &fasthttp.Server{ + Handler: server.handleRequest, + Name: "Moonshark", + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + MaxRequestBodySize: 16 << 20, // 16MB - consistent with Forms.go + DisableKeepalive: false, + TCPKeepalive: true, + TCPKeepalivePeriod: 60 * time.Second, + ReduceMemoryUsage: true, + GetOnly: false, + DisablePreParseMultipartForm: true, // We'll handle parsing manually } return server @@ -60,135 +64,130 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, runne // ListenAndServe starts the server on the given address func (s *Server) ListenAndServe(addr string) error { - s.httpServer.Addr = addr logger.ServerCont("Catch the swell at http://localhost%s", addr) - return s.httpServer.ListenAndServe() + return s.fasthttpServer.ListenAndServe(addr) } // Shutdown gracefully shuts down the server func (s *Server) Shutdown(ctx context.Context) error { - return s.httpServer.Shutdown(ctx) + return s.fasthttpServer.ShutdownWithContext(ctx) } -// ServeHTTP handles HTTP requests -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { +// 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 && r.URL.Path == "/debug/stats" { - s.handleDebugStats(w, r) + if s.debugMode && path == "/debug/stats" { + s.handleDebugStats(ctx) - // Calculate and log request duration - duration := time.Since(start) + // Log request if s.loggingEnabled { - LogRequest(http.StatusOK, r, duration) + duration := time.Since(start) + LogRequest(ctx.Response.StatusCode(), method, path, duration) } return } - // Wrap the ResponseWriter to capture status code - wrappedWriter := newStatusCaptureWriter(w) - // Process the request - s.handleRequest(wrappedWriter, r) - - // Calculate request duration - duration := time.Since(start) - - // Get the status code - statusCode := wrappedWriter.StatusCode() + s.processRequest(ctx) // Log the request with our custom format if s.loggingEnabled { - LogRequest(statusCode, r, duration) + duration := time.Since(start) + LogRequest(ctx.Response.StatusCode(), method, path, duration) } } -// handleRequest processes the actual request -func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) { - logger.Debug("Processing request %s %s", r.Method, r.URL.Path) +// 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(r.Method, r.URL.Path, 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 - this requires exposing the actual error - // from the node in the GetBytecode method + // 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(r.Method, r.URL.Path, params); node != nil && node.Error != nil { + if node, _ := s.luaRouter.GetNodeWithError(method, path, params); node != nil && node.Error != nil { errorMsg = node.Error.Error() } - logger.Error("%s %s - %s", r.Method, r.URL.Path, errorMsg) + logger.Error("%s %s - %s", method, 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)) + 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", r.Method, r.URL.Path, params.Count) - s.handleLuaRoute(w, r, bytecode, scriptPath, params) + 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 filePath, found := s.staticRouter.Match(r.URL.Path); found { - http.ServeFile(w, r, filePath) + if filePath, found := s.staticRouter.Match(path); found { + ctx.SendFile(filePath) return } // No route found - 404 Not Found - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(utils.NotFoundPage(s.errorConfig, r.URL.Path))) + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(fasthttp.StatusNotFound) + ctx.SetBody([]byte(utils.NotFoundPage(s.errorConfig, path))) } // HandleMethodNotAllowed responds with a 405 Method Not Allowed error -func (s *Server) HandleMethodNotAllowed(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte(utils.MethodNotAllowedPage(s.errorConfig, r.URL.Path))) +func HandleMethodNotAllowed(ctx *fasthttp.RequestCtx, errorConfig utils.ErrorPageConfig) { + path := string(ctx.Path()) + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed) + ctx.SetBody([]byte(utils.MethodNotAllowedPage(errorConfig, path))) } // handleLuaRoute executes a Lua route -func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode []byte, scriptPath string, params *routers.Params) { - ctx := runner.NewContext() - defer ctx.Release() +func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scriptPath string, params *routers.Params) { + luaCtx := runner.NewContext() + defer luaCtx.Release() - // Set up context exactly as the original - cookieMap := make(map[string]any) - for _, cookie := range r.Cookies() { - cookieMap[cookie.Name] = cookie.Value - } - ctx.Set("_request_cookies", cookieMap) - ctx.Set("method", r.Method) - ctx.Set("path", r.URL.Path) - ctx.Set("host", r.Host) + method := string(ctx.Method()) + path := string(ctx.Path()) + host := string(ctx.Host()) + + // Set up context + luaCtx.Set("method", method) + luaCtx.Set("path", path) + luaCtx.Set("host", host) // Headers - headerMap := make(map[string]any, len(r.Header)) - for name, values := range r.Header { - if len(values) == 1 { - headerMap[name] = values[0] - } else { - headerMap[name] = values - } - } - ctx.Set("headers", headerMap) + headerMap := make(map[string]any) + ctx.Request.Header.VisitAll(func(key, value []byte) { + headerMap[string(key)] = string(value) + }) + luaCtx.Set("headers", headerMap) // Cookies - if cookies := r.Cookies(); len(cookies) > 0 { - cookieMap := make(map[string]any, len(cookies)) - for _, cookie := range cookies { - cookieMap[cookie.Name] = cookie.Value - } - ctx.Set("cookies", cookieMap) + cookieMap := make(map[string]any) + ctx.Request.Header.VisitAllCookie(func(key, value []byte) { + cookieMap[string(key)] = string(value) + }) + if len(cookieMap) > 0 { + luaCtx.Set("cookies", cookieMap) + luaCtx.Set("_request_cookies", cookieMap) // For backward compatibility + } else { + luaCtx.Set("cookies", make(map[string]any)) + luaCtx.Set("_request_cookies", make(map[string]any)) } // URL parameters @@ -197,45 +196,51 @@ func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode for i, key := range params.Keys { paramMap[key] = params.Values[i] } - ctx.Set("params", paramMap) + luaCtx.Set("params", paramMap) + } else { + luaCtx.Set("params", make(map[string]any)) } // Query parameters - queryMap := QueryToLua(r) - if queryMap == nil { - ctx.Set("query", make(map[string]any)) - } else { - ctx.Set("query", queryMap) - } + queryMap := QueryToLua(ctx) + luaCtx.Set("query", queryMap) // Form data - if r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch { - if formData, err := ParseForm(r); err == nil && len(formData) > 0 { - ctx.Set("form", formData) + 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 - result, err := s.luaRunner.Run(bytecode, ctx, scriptPath) + result, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath) // Special handling for CSRF error if err != nil { if csrfErr, ok := err.(*runner.CSRFError); ok { logger.Warning("CSRF error executing Lua route: %v", csrfErr) - HandleCSRFError(w, r, s.errorConfig) + HandleCSRFError(ctx, s.errorConfig) return } // Normal error handling logger.Error("Error executing Lua route: %v", err) - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - errorHTML := utils.InternalErrorPage(s.errorConfig, r.URL.Path, err.Error()) - w.Write([]byte(errorHTML)) + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + errorHTML := utils.InternalErrorPage(s.errorConfig, path, err.Error()) + ctx.SetBody([]byte(errorHTML)) return } - writeResponse(w, result) + writeResponse(ctx, result) } // Content types for responses @@ -245,9 +250,9 @@ const ( ) // writeResponse writes the Lua result to the HTTP response -func writeResponse(w http.ResponseWriter, result any) { +func writeResponse(ctx *fasthttp.RequestCtx, result any) { if result == nil { - w.WriteHeader(http.StatusNoContent) + ctx.SetStatusCode(fasthttp.StatusNoContent) return } @@ -257,16 +262,34 @@ func writeResponse(w http.ResponseWriter, result any) { // Set response headers for name, value := range httpResp.Headers { - w.Header().Set(name, value) + ctx.Response.Header.Set(name, value) } // Set cookies for _, cookie := range httpResp.Cookies { - http.SetCookie(w, cookie) + // Convert net/http cookie to fasthttp cookie + var c fasthttp.Cookie + c.SetKey(cookie.Name) + c.SetValue(cookie.Value) + if cookie.Path != "" { + c.SetPath(cookie.Path) + } + if cookie.Domain != "" { + c.SetDomain(cookie.Domain) + } + if cookie.MaxAge > 0 { + c.SetMaxAge(cookie.MaxAge) + } + if cookie.Expires.After(time.Time{}) { + c.SetExpire(cookie.Expires) + } + c.SetSecure(cookie.Secure) + c.SetHTTPOnly(cookie.HttpOnly) + ctx.Response.Header.SetCookie(&c) } // Set status code - w.WriteHeader(httpResp.Status) + ctx.SetStatusCode(httpResp.Status) // Process the body based on its type if httpResp.Body == nil { @@ -285,39 +308,39 @@ func writeResponse(w http.ResponseWriter, result any) { } if isJSON { - setContentTypeIfMissing(w, contentTypeJSON) + setContentTypeIfMissing(ctx, contentTypeJSON) data, err := json.Marshal(result) if err != nil { logger.Error("Failed to marshal response: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) return } - w.Write(data) + ctx.SetBody(data) return } // All other types - convert to plain text - setContentTypeIfMissing(w, contentTypePlain) + setContentTypeIfMissing(ctx, contentTypePlain) switch r := result.(type) { case string: - w.Write([]byte(r)) + ctx.SetBodyString(r) case []byte: - w.Write(r) + ctx.SetBody(r) default: // Convert any other type to string - fmt.Fprintf(w, "%v", r) + ctx.SetBodyString(fmt.Sprintf("%v", r)) } } -func setContentTypeIfMissing(w http.ResponseWriter, contentType string) { - if w.Header().Get("Content-Type") == "" { - w.Header().Set("Content-Type", contentType) +func setContentTypeIfMissing(ctx *fasthttp.RequestCtx, contentType string) { + if len(ctx.Response.Header.ContentType()) == 0 { + ctx.SetContentType(contentType) } } // handleDebugStats displays debug statistics -func (s *Server) handleDebugStats(w http.ResponseWriter, _ *http.Request) { +func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) { // Collect system stats stats := utils.CollectSystemStats(s.config) @@ -335,7 +358,7 @@ func (s *Server) handleDebugStats(w http.ResponseWriter, _ *http.Request) { html := utils.DebugStatsPage(stats) // Send the response - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(html)) + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.SetBody([]byte(html)) } diff --git a/core/http/Status.go b/core/http/Status.go deleted file mode 100644 index 9b96ba5..0000000 --- a/core/http/Status.go +++ /dev/null @@ -1,33 +0,0 @@ -package http - -import ( - "net/http" -) - -// statusCaptureWriter is a ResponseWriter that captures the status code -type statusCaptureWriter struct { - http.ResponseWriter - statusCode int -} - -// WriteHeader captures the status code and passes it to the wrapped ResponseWriter -func (w *statusCaptureWriter) WriteHeader(code int) { - w.statusCode = code - w.ResponseWriter.WriteHeader(code) -} - -// StatusCode returns the captured status code -func (w *statusCaptureWriter) StatusCode() int { - if w.statusCode == 0 { - return http.StatusOK // Default to 200 if not explicitly set - } - return w.statusCode -} - -// newStatusCaptureWriter creates a new statusCaptureWriter -func newStatusCaptureWriter(w http.ResponseWriter) *statusCaptureWriter { - return &statusCaptureWriter{ - ResponseWriter: w, - statusCode: 0, - } -} diff --git a/go.mod b/go.mod index e114b05..e064685 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,11 @@ go 1.24.1 require git.sharkk.net/Sky/LuaJIT-to-Go v0.0.0 +require ( + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.60.0 // indirect +) + replace git.sharkk.net/Sky/LuaJIT-to-Go => ./luajit diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a682324 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= +github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=