interface{} to any

This commit is contained in:
Sky Johnson 2025-07-16 20:54:15 -05:00
parent 03d1b93f35
commit 4ff04e141d
6 changed files with 7 additions and 814 deletions

View File

@ -1,495 +0,0 @@
package http
import (
"fmt"
"sync"
"time"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
"github.com/valyala/fasthttp"
)
// Handler is a fasthttp request handler with parameters
type Handler func(ctx *fasthttp.RequestCtx, params []string)
type node struct {
segment string
handler Handler
children []*node
isDynamic bool
isWildcard bool
maxParams uint8
}
type Router struct {
get, post, put, patch, delete *node
paramsBuffer []string
}
func newRouter() *Router {
return &Router{
get: &node{},
post: &node{},
put: &node{},
patch: &node{},
delete: &node{},
paramsBuffer: make([]string, 64),
}
}
// HTTPServer with efficient serialized Lua handling
type HTTPServer struct {
server *fasthttp.Server
router *Router
addr string
running bool
mu sync.RWMutex
luaMu sync.Mutex // Serializes Lua calls
}
var (
serverRegistry = struct {
sync.RWMutex
servers map[int]*HTTPServer
nextID int
}{
servers: make(map[int]*HTTPServer),
nextID: 1,
}
)
func GetHTTPFunctions() map[string]luajit.GoFunction {
return map[string]luajit.GoFunction{
"http_create_server": func(s *luajit.State) int {
server := &HTTPServer{
server: &fasthttp.Server{
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
},
router: newRouter(),
}
server.server.Handler = server.handleRequest
serverRegistry.Lock()
id := serverRegistry.nextID
serverRegistry.nextID++
serverRegistry.servers[id] = server
serverRegistry.Unlock()
s.PushNumber(float64(id))
return 1
},
"http_server_listen": func(s *luajit.State) int {
if err := s.CheckExactArgs(2); err != nil {
return s.PushError("http_server_listen: %v", err)
}
serverID, err := s.SafeToNumber(1)
if err != nil || serverID != float64(int(serverID)) {
return s.PushError("http_server_listen: server ID must be an integer")
}
addr, err := s.SafeToString(2)
if err != nil {
return s.PushError("http_server_listen: address must be a string")
}
serverRegistry.RLock()
server, exists := serverRegistry.servers[int(serverID)]
serverRegistry.RUnlock()
if !exists {
return s.PushError("http_server_listen: server not found")
}
server.mu.Lock()
if server.running {
server.mu.Unlock()
return s.PushError("http_server_listen: server already running")
}
server.addr = addr
server.running = true
server.mu.Unlock()
go func() {
err := server.server.ListenAndServe(addr)
if err != nil {
server.mu.Lock()
server.running = false
server.mu.Unlock()
}
}()
s.PushBoolean(true)
return 1
},
"http_server_stop": func(s *luajit.State) int {
if err := s.CheckMinArgs(1); err != nil {
return s.PushError("http_server_stop: %v", err)
}
serverID, err := s.SafeToNumber(1)
if err != nil || serverID != float64(int(serverID)) {
return s.PushError("http_server_stop: server ID must be an integer")
}
serverRegistry.RLock()
server, exists := serverRegistry.servers[int(serverID)]
serverRegistry.RUnlock()
if !exists {
return s.PushError("http_server_stop: server not found")
}
server.mu.Lock()
if !server.running {
server.mu.Unlock()
s.PushBoolean(false)
return 1
}
server.running = false
server.mu.Unlock()
if err := server.server.Shutdown(); err != nil {
return s.PushError("http_server_stop: %v", err)
}
s.PushBoolean(true)
return 1
},
"http_server_get": createRouteHandler("GET"),
"http_server_post": createRouteHandler("POST"),
"http_server_put": createRouteHandler("PUT"),
"http_server_patch": createRouteHandler("PATCH"),
"http_server_delete": createRouteHandler("DELETE"),
"http_server_is_running": func(s *luajit.State) int {
if err := s.CheckMinArgs(1); err != nil {
return s.PushError("http_server_is_running: %v", err)
}
serverID, err := s.SafeToNumber(1)
if err != nil || serverID != float64(int(serverID)) {
return s.PushError("http_server_is_running: server ID must be an integer")
}
serverRegistry.RLock()
server, exists := serverRegistry.servers[int(serverID)]
serverRegistry.RUnlock()
if !exists {
s.PushBoolean(false)
return 1
}
server.mu.RLock()
running := server.running
server.mu.RUnlock()
s.PushBoolean(running)
return 1
},
"http_cleanup_servers": func(s *luajit.State) int {
serverRegistry.Lock()
for id, server := range serverRegistry.servers {
server.mu.Lock()
if server.running {
server.server.Shutdown()
server.running = false
}
server.mu.Unlock()
delete(serverRegistry.servers, id)
}
serverRegistry.Unlock()
s.PushBoolean(true)
return 1
},
}
}
func createRouteHandler(method string) luajit.GoFunction {
return func(s *luajit.State) int {
if err := s.CheckExactArgs(3); err != nil {
return s.PushError("http_server_%s: %v", method, err)
}
serverID, err := s.SafeToNumber(1)
if err != nil || serverID != float64(int(serverID)) {
return s.PushError("http_server_%s: server ID must be an integer", method)
}
path, err := s.SafeToString(2)
if err != nil {
return s.PushError("http_server_%s: path must be a string", method)
}
if !s.IsFunction(3) {
return s.PushError("http_server_%s: handler must be a function", method)
}
serverRegistry.RLock()
server, exists := serverRegistry.servers[int(serverID)]
serverRegistry.RUnlock()
if !exists {
return s.PushError("http_server_%s: server not found", method)
}
luaFunc, err := s.StoreLuaFunction(3)
if err != nil {
return s.PushError("http_server_%s: failed to store function: %v", method, err)
}
handler := func(ctx *fasthttp.RequestCtx, params []string) {
server.callLuaHandler(ctx, params, luaFunc)
}
if err := server.router.addRoute(method, path, handler); err != nil {
return s.PushError("http_server_%s: failed to add route: %v", method, err)
}
s.PushBoolean(true)
return 1
}
}
// Router methods
func (r *Router) methodNode(method string) *node {
switch method {
case "GET":
return r.get
case "POST":
return r.post
case "PUT":
return r.put
case "PATCH":
return r.patch
case "DELETE":
return r.delete
default:
return nil
}
}
func (r *Router) addRoute(method, path string, h Handler) error {
root := r.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
if path == "/" {
root.handler = h
return nil
}
current := root
pos := 0
lastWC := false
count := uint8(0)
for {
seg, newPos, more := readSegment(path, pos)
if seg == "" {
break
}
isDyn := len(seg) > 0 && seg[0] == ':'
isWC := len(seg) > 0 && seg[0] == '*'
if isWC {
if lastWC || more {
return fmt.Errorf("wildcard must be the last segment in the path")
}
lastWC = true
}
if isDyn || isWC {
count++
}
var child *node
for _, c := range current.children {
if c.segment == seg {
child = c
break
}
}
if child == nil {
child = &node{segment: seg, isDynamic: isDyn, isWildcard: isWC}
current.children = append(current.children, child)
}
if child.maxParams < count {
child.maxParams = count
}
current = child
pos = newPos
}
current.handler = h
return nil
}
func (r *Router) lookup(method, path string) (Handler, []string, bool) {
root := r.methodNode(method)
if root == nil {
return nil, nil, false
}
if path == "/" {
return root.handler, nil, root.handler != nil
}
buffer := r.paramsBuffer
if cap(buffer) < int(root.maxParams) {
buffer = make([]string, root.maxParams)
r.paramsBuffer = buffer
}
buffer = buffer[:0]
h, paramCount, found := match(root, path, 0, &buffer)
if !found {
return nil, nil, false
}
return h, buffer[:paramCount], true
}
// HTTPServer methods
func (hs *HTTPServer) handleRequest(ctx *fasthttp.RequestCtx) {
method := string(ctx.Method())
path := string(ctx.Path())
handler, params, found := hs.router.lookup(method, path)
if !found {
ctx.SetStatusCode(fasthttp.StatusNotFound)
ctx.WriteString("Not Found")
return
}
handler(ctx, params)
}
func (hs *HTTPServer) callLuaHandler(ctx *fasthttp.RequestCtx, params []string, handler *luajit.LuaFunction) {
hs.luaMu.Lock()
defer hs.luaMu.Unlock()
request := map[string]interface{}{
"method": string(ctx.Method()),
"path": string(ctx.Path()),
"query": string(ctx.QueryArgs().QueryString()),
"headers": make(map[string]string),
"body": string(ctx.PostBody()),
"remote": ctx.RemoteAddr().String(),
"params": params,
}
headers := request["headers"].(map[string]string)
ctx.Request.Header.VisitAll(func(key, value []byte) {
headers[string(key)] = string(value)
})
response := map[string]interface{}{
"status": 200,
"headers": make(map[string]string),
"body": "",
}
results, err := handler.Call(request, response)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.WriteString(fmt.Sprintf("Handler error: %v", err))
return
}
if len(results) > 0 {
if respMap, ok := results[0].(map[string]interface{}); ok {
response = respMap
}
}
if status, ok := response["status"].(int); ok {
ctx.SetStatusCode(status)
} else if status, ok := response["status"].(float64); ok {
ctx.SetStatusCode(int(status))
}
if headers, ok := response["headers"].(map[string]interface{}); ok {
for k, v := range headers {
if str, ok := v.(string); ok {
ctx.Response.Header.Set(k, str)
}
}
}
if body, ok := response["body"].(string); ok {
ctx.WriteString(body)
}
}
// Utility functions
func readSegment(path string, start int) (segment string, end int, hasMore bool) {
if start >= len(path) {
return "", start, false
}
if path[start] == '/' {
start++
}
if start >= len(path) {
return "", start, false
}
end = start
for end < len(path) && path[end] != '/' {
end++
}
return path[start:end], end, end < len(path)
}
func match(current *node, path string, start int, params *[]string) (Handler, int, bool) {
paramCount := 0
for _, c := range current.children {
if c.isWildcard {
rem := path[start:]
if len(rem) > 0 && rem[0] == '/' {
rem = rem[1:]
}
*params = append(*params, rem)
return c.handler, 1, c.handler != nil
}
}
seg, pos, more := readSegment(path, start)
if seg == "" {
return current.handler, 0, current.handler != nil
}
for _, c := range current.children {
if c.segment == seg || c.isDynamic {
if c.isDynamic {
*params = append(*params, seg)
paramCount++
}
if !more {
return c.handler, paramCount, c.handler != nil
}
h, nestedCount, ok := match(c, path, pos, params)
if ok {
return h, paramCount + nestedCount, true
}
}
}
return nil, 0, false
}

View File

@ -37,7 +37,7 @@ func GetJSONFunctions() map[string]luajit.GoFunction {
return s.PushError("json_decode: input must be a string")
}
var result interface{}
var result any
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
// Return nil and error string instead of PushError for JSON parsing errors
s.PushNil()

View File

@ -4,8 +4,6 @@ import (
"maps"
"sync"
"Moonshark/functions/http"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
@ -21,7 +19,6 @@ func GetAll() Registry {
maps.Copy(registry, GetMathFunctions())
maps.Copy(registry, GetFSFunctions())
maps.Copy(registry, GetCryptoFunctions())
maps.Copy(registry, http.GetHTTPFunctions())
return registry
}

View File

@ -84,7 +84,7 @@ func GetStringFunctions() map[string]luajit.GoFunction {
switch v := arr.(type) {
case []string:
parts = v
case []interface{}:
case []any:
parts = make([]string, len(v))
for i, val := range v {
if val == nil {
@ -93,7 +93,7 @@ func GetStringFunctions() map[string]luajit.GoFunction {
parts[i] = fmt.Sprintf("%v", val)
}
}
case map[string]interface{}:
case map[string]any:
if len(v) == 0 {
parts = []string{}
} else {

View File

@ -1,277 +0,0 @@
-- modules/http.lua - HTTP server with Go-based routing
local http = {}
-- ======================================================================
-- HTTP SERVER
-- ======================================================================
local HTTPServer = {}
HTTPServer.__index = HTTPServer
function HTTPServer:listen(addr)
local success, err = moonshark.http_server_listen(self.id, addr)
if not success then
error("Failed to start server: " .. (err or "unknown error"))
end
self.addr = addr
return self
end
function HTTPServer:stop()
local success, err = moonshark.http_server_stop(self.id)
if not success then
error("Failed to stop server: " .. (err or "unknown error"))
end
return self
end
function HTTPServer:get(path, handler)
if type(handler) ~= "function" then
error("Handler must be a function")
end
local success, err = moonshark.http_server_get(self.id, path, handler)
if not success then
error("Failed to add GET route: " .. (err or "unknown error"))
end
return self
end
function HTTPServer:post(path, handler)
if type(handler) ~= "function" then
error("Handler must be a function")
end
local success, err = moonshark.http_server_post(self.id, path, handler)
if not success then
error("Failed to add POST route: " .. (err or "unknown error"))
end
return self
end
function HTTPServer:put(path, handler)
if type(handler) ~= "function" then
error("Handler must be a function")
end
local success, err = moonshark.http_server_put(self.id, path, handler)
if not success then
error("Failed to add PUT route: " .. (err or "unknown error"))
end
return self
end
function HTTPServer:patch(path, handler)
if type(handler) ~= "function" then
error("Handler must be a function")
end
local success, err = moonshark.http_server_patch(self.id, path, handler)
if not success then
error("Failed to add PATCH route: " .. (err or "unknown error"))
end
return self
end
function HTTPServer:delete(path, handler)
if type(handler) ~= "function" then
error("Handler must be a function")
end
local success, err = moonshark.http_server_delete(self.id, path, handler)
if not success then
error("Failed to add DELETE route: " .. (err or "unknown error"))
end
return self
end
function HTTPServer:isRunning()
return moonshark.http_server_is_running(self.id)
end
-- Handle cleanup when server is garbage collected
function HTTPServer:__gc()
if self:isRunning() then
pcall(function() self:stop() end)
end
end
-- ======================================================================
-- HTTP MODULE FUNCTIONS
-- ======================================================================
function http.newServer(options)
options = options or {}
local serverID = moonshark.http_create_server()
if not serverID then
error("Failed to create HTTP server")
end
local server = setmetatable({
id = serverID,
addr = nil
}, HTTPServer)
return server
end
-- Convenience function to create and start server in one call
function http.listen(addr, handler)
local server = http.newServer()
if handler then
server:get("/*", handler)
end
return server:listen(addr)
end
-- Clean up all servers
function http.cleanup()
moonshark.http_cleanup_servers()
end
-- ======================================================================
-- REQUEST/RESPONSE HELPERS
-- ======================================================================
http.status = {
OK = 200,
CREATED = 201,
NO_CONTENT = 204,
MOVED_PERMANENTLY = 301,
FOUND = 302,
NOT_MODIFIED = 304,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
CONFLICT = 409,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503
}
-- Create a response object
function http.response(status, body, headers)
return {
status = status or http.status.OK,
body = body or "",
headers = headers or {}
}
end
-- Create JSON response
function http.json(data, status)
local json_str = moonshark.json_encode(data)
return {
status = status or http.status.OK,
body = json_str,
headers = {
["Content-Type"] = "application/json"
}
}
end
-- Create text response
function http.text(text, status)
return {
status = status or http.status.OK,
body = tostring(text),
headers = {
["Content-Type"] = "text/plain"
}
}
end
-- Create HTML response
function http.html(html, status)
return {
status = status or http.status.OK,
body = tostring(html),
headers = {
["Content-Type"] = "text/html"
}
}
end
-- Redirect response
function http.redirect(location, status)
return {
status = status or http.status.FOUND,
body = "",
headers = {
["Location"] = location
}
}
end
-- Error response
function http.error(message, status)
return http.json({
error = message or "Internal Server Error"
}, status or http.status.INTERNAL_SERVER_ERROR)
end
-- ======================================================================
-- UTILITY FUNCTIONS
-- ======================================================================
-- Parse query string
function http.parseQuery(queryString)
local params = {}
if not queryString or queryString == "" then
return params
end
for pair in queryString:gmatch("[^&]+") do
local key, value = pair:match("([^=]+)=?(.*)")
if key then
key = http.urlDecode(key)
value = http.urlDecode(value or "")
params[key] = value
end
end
return params
end
-- URL decode
function http.urlDecode(str)
if not str then return "" end
str = str:gsub("+", " ")
str = str:gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
return str
end
-- URL encode
function http.urlEncode(str)
if not str then return "" end
str = str:gsub("([^%w%-%.%_%~])", function(c)
return string.format("%%%02X", string.byte(c))
end)
return str
end
-- Parse cookies
function http.parseCookies(cookieHeader)
local cookies = {}
if not cookieHeader then return cookies end
for pair in cookieHeader:gmatch("[^;]+") do
local key, value = pair:match("^%s*([^=]+)=?(.*)")
if key then
cookies[key:match("^%s*(.-)%s*$")] = value and value:match("^%s*(.-)%s*$") or ""
end
end
return cookies
end
return http

View File

@ -1,12 +1,11 @@
package main
import (
"Moonshark/functions"
"Moonshark/modules"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
@ -74,43 +73,12 @@ func main() {
os.Exit(1)
}
// Store the bytecode so our Go module implementations can use it elsewhere
functions.SetStoredBytecode(bytecode)
// Execute the compiled bytecode
if err := state.LoadAndRunBytecode(bytecode, scriptPath); err != nil {
fmt.Fprintf(os.Stderr, "Error executing '%s': %v\n", scriptPath, err)
os.Exit(1)
}
// Check if any servers are running to determine if we need to wait
hasRunningServers := false
if result, err := state.ExecuteWithResult(`
for i = 1, 100 do
if moonshark.http_server_is_running(i) then
return true
end
end
return false
`); err == nil {
if running, ok := result.(bool); ok && running {
hasRunningServers = true
}
}
// Only set up signal handling if there are long-running services
if hasRunningServers {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
fmt.Printf("Script executed successfully. Press Ctrl+C to stop...\n")
// Wait for signal
<-sigChan
fmt.Printf("\nReceived shutdown signal. Cleaning up...\n")
// Cleanup HTTP servers through Lua
if err := state.DoString("moonshark.http_cleanup_servers()"); err != nil {
fmt.Printf("Warning: failed to cleanup servers: %v\n", err)
}
fmt.Printf("Shutdown complete.\n")
}
}