runner rework 1

This commit is contained in:
Sky Johnson 2025-04-09 16:19:51 -05:00
parent 5ba0a0abd9
commit f4b1e5fad7
7 changed files with 225 additions and 24 deletions

View File

@ -2,6 +2,7 @@ package http
import ( import (
"Moonshark/core/runner" "Moonshark/core/runner"
luaCtx "Moonshark/core/runner/context"
"Moonshark/core/utils" "Moonshark/core/utils"
"Moonshark/core/utils/logger" "Moonshark/core/utils/logger"
"crypto/subtle" "crypto/subtle"
@ -12,7 +13,7 @@ import (
) )
// ValidateCSRFToken checks if the CSRF token is valid for a request // ValidateCSRFToken checks if the CSRF token is valid for a request
func ValidateCSRFToken(state *luajit.State, ctx *runner.Context) bool { func ValidateCSRFToken(state *luajit.State, ctx *luaCtx.Context) bool {
// Only validate for form submissions // Only validate for form submissions
method, ok := ctx.Get("method").(string) method, ok := ctx.Get("method").(string)
if !ok || (method != "POST" && method != "PUT" && method != "PATCH" && method != "DELETE") { if !ok || (method != "POST" && method != "PUT" && method != "PATCH" && method != "DELETE") {
@ -73,7 +74,7 @@ func ValidateCSRFToken(state *luajit.State, ctx *runner.Context) bool {
// WithCSRFProtection creates a runner option to add CSRF protection // WithCSRFProtection creates a runner option to add CSRF protection
func WithCSRFProtection() runner.RunnerOption { func WithCSRFProtection() runner.RunnerOption {
return func(r *runner.Runner) { return func(r *runner.Runner) {
r.AddInitHook(func(state *luajit.State, ctx *runner.Context) error { r.AddInitHook(func(state *luajit.State, ctx *luaCtx.Context) error {
// Get request method // Get request method
method, ok := ctx.Get("method").(string) method, ok := ctx.Get("method").(string)
if !ok { if !ok {

View File

@ -9,7 +9,9 @@ import (
"Moonshark/core/metadata" "Moonshark/core/metadata"
"Moonshark/core/routers" "Moonshark/core/routers"
"Moonshark/core/runner" "Moonshark/core/runner"
luaCtx "Moonshark/core/runner/context"
"Moonshark/core/runner/sandbox" "Moonshark/core/runner/sandbox"
"Moonshark/core/sessions"
"Moonshark/core/utils" "Moonshark/core/utils"
"Moonshark/core/utils/config" "Moonshark/core/utils/config"
"Moonshark/core/utils/logger" "Moonshark/core/utils/logger"
@ -160,8 +162,9 @@ func HandleMethodNotAllowed(ctx *fasthttp.RequestCtx, errorConfig utils.ErrorPag
} }
// handleLuaRoute executes a Lua route // handleLuaRoute executes a Lua route
// Updated handleLuaRoute function to handle sessions
func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scriptPath string, params *routers.Params) { func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scriptPath string, params *routers.Params) {
luaCtx := runner.NewHTTPContext(ctx) // Use NewHTTPContext instead of NewContext luaCtx := luaCtx.NewHTTPContext(ctx)
defer luaCtx.Release() defer luaCtx.Release()
method := string(ctx.Method()) method := string(ctx.Method())
@ -223,6 +226,27 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip
luaCtx.Set("form", make(map[string]any)) luaCtx.Set("form", make(map[string]any))
} }
// Session handling
cookieOpts := sessions.GlobalSessionManager.CookieOptions()
cookieName := cookieOpts["name"].(string)
sessionCookie := ctx.Request.Header.Cookie(cookieName)
var sessionID string
if sessionCookie != nil {
sessionID = string(sessionCookie)
}
// Get or create session
var session *sessions.Session
if sessionID != "" {
session = sessions.GlobalSessionManager.GetSession(sessionID)
} else {
session = sessions.GlobalSessionManager.CreateSession()
}
// Set session in context
luaCtx.Session = session
// Execute Lua script // Execute Lua script
result, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath) result, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath)
@ -243,6 +267,31 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip
return return
} }
// Handle session updates if needed
if luaCtx.SessionModified {
sessions.GlobalSessionManager.SaveSession(luaCtx.Session)
// Set session cookie
cookie := fasthttp.AcquireCookie()
cookie.SetKey(cookieName)
cookie.SetValue(luaCtx.Session.ID)
cookie.SetPath(cookieOpts["path"].(string))
if domain, ok := cookieOpts["domain"].(string); ok && domain != "" {
cookie.SetDomain(domain)
}
if maxAge, ok := cookieOpts["max_age"].(int); ok {
cookie.SetMaxAge(maxAge)
}
cookie.SetSecure(cookieOpts["secure"].(bool))
cookie.SetHTTPOnly(cookieOpts["http_only"].(bool))
ctx.Response.Header.SetCookie(cookie)
fasthttp.ReleaseCookie(cookie)
}
// If we got a non-nil result, write it to the response // If we got a non-nil result, write it to the response
if result != nil { if result != nil {
writeResponse(ctx, result) writeResponse(ctx, result)

View File

@ -9,6 +9,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
luaCtx "Moonshark/core/runner/context"
"Moonshark/core/runner/sandbox" "Moonshark/core/runner/sandbox"
"Moonshark/core/utils/logger" "Moonshark/core/utils/logger"
@ -35,10 +36,10 @@ type State struct {
} }
// InitHook runs before executing a script // InitHook runs before executing a script
type InitHook func(*luajit.State, *Context) error type InitHook func(*luajit.State, *luaCtx.Context) error
// FinalizeHook runs after executing a script // FinalizeHook runs after executing a script
type FinalizeHook func(*luajit.State, *Context, any) error type FinalizeHook func(*luajit.State, *luaCtx.Context, any) error
// Runner runs Lua scripts using a pool of Lua states // Runner runs Lua scripts using a pool of Lua states
type Runner struct { type Runner struct {
@ -216,7 +217,7 @@ func (r *Runner) createState(index int) (*State, error) {
} }
// Execute runs a script with context // Execute runs a script with context
func (r *Runner) Execute(ctx context.Context, bytecode []byte, execCtx *Context, scriptPath string) (any, error) { func (r *Runner) Execute(ctx context.Context, bytecode []byte, execCtx *luaCtx.Context, scriptPath string) (any, error) {
if !r.isRunning.Load() { if !r.isRunning.Load() {
return nil, ErrRunnerClosed return nil, ErrRunnerClosed
} }
@ -282,7 +283,7 @@ func (r *Runner) Execute(ctx context.Context, bytecode []byte, execCtx *Context,
if execCtx != nil && execCtx.RequestCtx != nil { if execCtx != nil && execCtx.RequestCtx != nil {
// Use OptimizedExecute directly with the full context if we have RequestCtx // Use OptimizedExecute directly with the full context if we have RequestCtx
result, err = state.sandbox.OptimizedExecute(state.L, bytecode, &sandbox.Context{ result, err = state.sandbox.OptimizedExecute(state.L, bytecode, &luaCtx.Context{
Values: ctxValues, Values: ctxValues,
RequestCtx: execCtx.RequestCtx, RequestCtx: execCtx.RequestCtx,
}) })
@ -326,7 +327,7 @@ func (r *Runner) Execute(ctx context.Context, bytecode []byte, execCtx *Context,
} }
// Run executes a Lua script (convenience wrapper) // Run executes a Lua script (convenience wrapper)
func (r *Runner) Run(bytecode []byte, execCtx *Context, scriptPath string) (any, error) { func (r *Runner) Run(bytecode []byte, execCtx *luaCtx.Context, scriptPath string) (any, error) {
return r.Execute(context.Background(), bytecode, execCtx, scriptPath) return r.Execute(context.Background(), bytecode, execCtx, scriptPath)
} }

View File

@ -1,6 +1,7 @@
package runner package runner
import ( import (
luaCtx "Moonshark/core/runner/context"
"Moonshark/core/runner/sandbox" "Moonshark/core/runner/sandbox"
"Moonshark/core/sessions" "Moonshark/core/sessions"
"Moonshark/core/utils/logger" "Moonshark/core/utils/logger"
@ -38,7 +39,7 @@ func WithSessionManager(manager *sessions.SessionManager) RunnerOption {
} }
// preRequestHook initializes session before script execution // preRequestHook initializes session before script execution
func (h *SessionHandler) preRequestHook(state *luajit.State, ctx *Context) error { func (h *SessionHandler) preRequestHook(state *luajit.State, ctx *luaCtx.Context) error {
if ctx == nil || ctx.Values["_request_cookies"] == nil { if ctx == nil || ctx.Values["_request_cookies"] == nil {
return nil return nil
} }
@ -78,7 +79,7 @@ func (h *SessionHandler) preRequestHook(state *luajit.State, ctx *Context) error
} }
// postRequestHook handles session after script execution // postRequestHook handles session after script execution
func (h *SessionHandler) postRequestHook(state *luajit.State, ctx *Context, result any) error { func (h *SessionHandler) postRequestHook(state *luajit.State, ctx *luaCtx.Context, result any) error {
// Check if session was modified // Check if session was modified
modifiedID, modifiedData, modified := GetSessionData(state) modifiedID, modifiedData, modified := GetSessionData(state)
if !modified { if !modified {

View File

@ -3,6 +3,8 @@ package runner
import ( import (
"sync" "sync"
"Moonshark/core/sessions"
"github.com/valyala/bytebufferpool" "github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@ -15,6 +17,10 @@ type Context struct {
// FastHTTP context if this was created from an HTTP request // FastHTTP context if this was created from an HTTP request
RequestCtx *fasthttp.RequestCtx RequestCtx *fasthttp.RequestCtx
// Session data and management
Session *sessions.Session
SessionModified bool
// Buffer for efficient string operations // Buffer for efficient string operations
buffer *bytebufferpool.ByteBuffer buffer *bytebufferpool.ByteBuffer
} }
@ -47,6 +53,10 @@ func (c *Context) Release() {
delete(c.Values, k) delete(c.Values, k)
} }
// Reset session info
c.Session = nil
c.SessionModified = false
// Reset request context // Reset request context
c.RequestCtx = nil c.RequestCtx = nil

View File

@ -18,12 +18,19 @@ import (
luajit "git.sharkk.net/Sky/LuaJIT-to-Go" luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
) )
// SessionHandler interface for session management
type SessionHandler interface {
LoadSession(ctx *fasthttp.RequestCtx) (string, map[string]any)
SaveSession(ctx *fasthttp.RequestCtx, sessionID string, data map[string]any) bool
}
// HTTPResponse represents an HTTP response from Lua // HTTPResponse represents an HTTP response from Lua
type HTTPResponse struct { type HTTPResponse struct {
Status int `json:"status"` Status int `json:"status"`
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
Body any `json:"body"` Body any `json:"body"`
Cookies []*fasthttp.Cookie `json:"-"` Cookies []*fasthttp.Cookie `json:"-"`
SessionModified bool `json:"-"`
} }
// Response pool to reduce allocations // Response pool to reduce allocations
@ -84,6 +91,9 @@ func ReleaseResponse(resp *HTTPResponse) {
// Clear cookies // Clear cookies
resp.Cookies = resp.Cookies[:0] // Keep capacity but set length to 0 resp.Cookies = resp.Cookies[:0] // Keep capacity but set length to 0
// Reset session flag
resp.SessionModified = false
// Clear body // Clear body
resp.Body = nil resp.Body = nil
@ -190,6 +200,13 @@ func GetHTTPResponse(state *luajit.State) (*HTTPResponse, bool) {
} }
state.Pop(1) state.Pop(1)
// Check if session was modified
state.GetGlobal("__session_modified")
if state.IsBoolean(-1) && state.ToBoolean(-1) {
response.SessionModified = true
}
state.Pop(1)
// Clean up // Clean up
state.Pop(2) // Pop response table and __http_responses state.Pop(2) // Pop response table and __http_responses

View File

@ -8,6 +8,8 @@ import (
"github.com/valyala/bytebufferpool" "github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
luaCtx "Moonshark/core/runner/context"
"Moonshark/core/sessions"
"Moonshark/core/utils/logger" "Moonshark/core/utils/logger"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go" luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
@ -126,7 +128,7 @@ func (s *Sandbox) Setup(state *luajit.State, stateIndex int) error {
func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx map[string]any) (any, error) { func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx map[string]any) (any, error) {
// Create a temporary context if we only have a map // Create a temporary context if we only have a map
if ctx != nil { if ctx != nil {
tempCtx := &Context{ tempCtx := &luaCtx.Context{
Values: ctx, Values: ctx,
} }
return s.OptimizedExecute(state, bytecode, tempCtx) return s.OptimizedExecute(state, bytecode, tempCtx)
@ -136,16 +138,8 @@ func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx map[string]a
return s.OptimizedExecute(state, bytecode, nil) return s.OptimizedExecute(state, bytecode, nil)
} }
// Context represents execution context for a Lua script
type Context struct {
// Values stores any context values (route params, HTTP request info, etc.)
Values map[string]any
// RequestCtx for HTTP requests
RequestCtx *fasthttp.RequestCtx
}
// OptimizedExecute runs bytecode with a fasthttp context if available // OptimizedExecute runs bytecode with a fasthttp context if available
func (s *Sandbox) OptimizedExecute(state *luajit.State, bytecode []byte, ctx *Context) (any, error) { func (s *Sandbox) OptimizedExecute(state *luajit.State, bytecode []byte, ctx *luaCtx.Context) (any, error) {
// Use a buffer from the pool for any string operations // Use a buffer from the pool for any string operations
buf := bytebufferpool.Get() buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf) defer bytebufferpool.Put(buf)
@ -164,6 +158,35 @@ func (s *Sandbox) OptimizedExecute(state *luajit.State, bytecode []byte, ctx *Co
ctxValues = nil ctxValues = nil
} }
// Initialize session tracking in Lua
if err := state.DoString("__session_data = {}; __session_modified = false"); err != nil {
s.debugLog("Failed to initialize session data: %v", err)
}
// Load session data if available
if ctx != nil && ctx.Session != nil {
// Set session ID in Lua
sessionIDCode := fmt.Sprintf("__session_id = %q", ctx.Session.ID)
if err := state.DoString(sessionIDCode); err != nil {
s.debugLog("Failed to set session ID: %v", err)
}
// Get session data and populate Lua table
state.GetGlobal("__session_data")
if state.IsTable(-1) {
sessionData := ctx.Session.GetAll()
for k, v := range sessionData {
state.PushString(k)
if err := state.PushValue(v); err != nil {
s.debugLog("Failed to push session value %s: %v", k, err)
continue
}
state.SetTable(-3)
}
}
state.Pop(1) // Pop __session_data
}
// Prepare context table // Prepare context table
if ctxValues != nil { if ctxValues != nil {
state.CreateTable(0, len(ctxValues)) state.CreateTable(0, len(ctxValues))
@ -206,14 +229,85 @@ func (s *Sandbox) OptimizedExecute(state *luajit.State, bytecode []byte, ctx *Co
result, err := state.ToValue(-1) result, err := state.ToValue(-1)
state.Pop(1) // Pop result state.Pop(1) // Pop result
// Extract session data if it was modified
if ctx != nil && ctx.Session != nil {
// Check if session was modified
state.GetGlobal("__session_modified")
if state.IsBoolean(-1) && state.ToBoolean(-1) {
ctx.SessionModified = true
// Extract session data
state.GetGlobal("__session_data")
if state.IsTable(-1) {
// Clear existing data and extract new data from Lua
sessionData := make(map[string]any)
// Extract new session data
state.PushNil() // Start iteration
for state.Next(-2) {
// Stack now has key at -2 and value at -1
if state.IsString(-2) {
key := state.ToString(-2)
value, err := state.ToValue(-1)
if err == nil {
sessionData[key] = value
}
}
state.Pop(1) // Pop value, leave key for next iteration
}
// Update session with the new data
for k, v := range sessionData {
if err := ctx.Session.Set(k, v); err != nil {
s.debugLog("Failed to set session value %s: %v", k, err)
}
}
}
state.Pop(1) // Pop __session_data
}
state.Pop(1) // Pop __session_modified
}
// Check for HTTP response // Check for HTTP response
httpResponse, hasResponse := GetHTTPResponse(state) httpResponse, hasResponse := GetHTTPResponse(state)
if hasResponse { if hasResponse {
// Add the script result as the response body // Add the script result as the response body
httpResponse.Body = result httpResponse.Body = result
// Mark session as modified if needed
if ctx != nil && ctx.SessionModified {
httpResponse.SessionModified = true
}
// If we have a fasthttp context, apply the response directly // If we have a fasthttp context, apply the response directly
if ctx != nil && ctx.RequestCtx != nil { if ctx != nil && ctx.RequestCtx != nil {
// If session was modified, save it
if ctx.SessionModified && ctx.Session != nil {
// Save session and set cookie if needed
sessions.GlobalSessionManager.SaveSession(ctx.Session)
// Add session cookie to the response
cookieOpts := sessions.GlobalSessionManager.CookieOptions()
cookie := fasthttp.AcquireCookie()
cookie.SetKey(cookieOpts["name"].(string))
cookie.SetValue(ctx.Session.ID)
cookie.SetPath(cookieOpts["path"].(string))
if domain, ok := cookieOpts["domain"].(string); ok && domain != "" {
cookie.SetDomain(domain)
}
if maxAge, ok := cookieOpts["max_age"].(int); ok && maxAge > 0 {
cookie.SetMaxAge(maxAge)
}
cookie.SetSecure(cookieOpts["secure"].(bool))
cookie.SetHTTPOnly(cookieOpts["http_only"].(bool))
// Add to response cookies
httpResponse.Cookies = append(httpResponse.Cookies, cookie)
}
ApplyHTTPResponse(httpResponse, ctx.RequestCtx) ApplyHTTPResponse(httpResponse, ctx.RequestCtx)
ReleaseResponse(httpResponse) ReleaseResponse(httpResponse)
return nil, nil // No need to return response object return nil, nil // No need to return response object
@ -242,6 +336,34 @@ func (s *Sandbox) OptimizedExecute(state *luajit.State, bytecode []byte, ctx *Co
// Default string conversion // Default string conversion
ctx.RequestCtx.SetBodyString(fmt.Sprintf("%v", r)) ctx.RequestCtx.SetBodyString(fmt.Sprintf("%v", r))
} }
// Handle session if modified
if ctx.SessionModified && ctx.Session != nil {
// Save session
sessions.GlobalSessionManager.SaveSession(ctx.Session)
// Add session cookie
cookieOpts := sessions.GlobalSessionManager.CookieOptions()
cookie := fasthttp.AcquireCookie()
cookie.SetKey(cookieOpts["name"].(string))
cookie.SetValue(ctx.Session.ID)
cookie.SetPath(cookieOpts["path"].(string))
if domain, ok := cookieOpts["domain"].(string); ok && domain != "" {
cookie.SetDomain(domain)
}
if maxAge, ok := cookieOpts["max_age"].(int); ok && maxAge > 0 {
cookie.SetMaxAge(maxAge)
}
cookie.SetSecure(cookieOpts["secure"].(bool))
cookie.SetHTTPOnly(cookieOpts["http_only"].(bool))
// Add to response
ctx.RequestCtx.Response.Header.SetCookie(cookie)
}
return nil, nil return nil, nil
} }