interface{} to any
This commit is contained in:
parent
03d1b93f35
commit
4ff04e141d
@ -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
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ func GetJSONFunctions() map[string]luajit.GoFunction {
|
|||||||
return s.PushError("json_decode: input must be a string")
|
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 {
|
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
|
||||||
// Return nil and error string instead of PushError for JSON parsing errors
|
// Return nil and error string instead of PushError for JSON parsing errors
|
||||||
s.PushNil()
|
s.PushNil()
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"Moonshark/functions/http"
|
|
||||||
|
|
||||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +19,6 @@ func GetAll() Registry {
|
|||||||
maps.Copy(registry, GetMathFunctions())
|
maps.Copy(registry, GetMathFunctions())
|
||||||
maps.Copy(registry, GetFSFunctions())
|
maps.Copy(registry, GetFSFunctions())
|
||||||
maps.Copy(registry, GetCryptoFunctions())
|
maps.Copy(registry, GetCryptoFunctions())
|
||||||
maps.Copy(registry, http.GetHTTPFunctions())
|
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
switch v := arr.(type) {
|
switch v := arr.(type) {
|
||||||
case []string:
|
case []string:
|
||||||
parts = v
|
parts = v
|
||||||
case []interface{}:
|
case []any:
|
||||||
parts = make([]string, len(v))
|
parts = make([]string, len(v))
|
||||||
for i, val := range v {
|
for i, val := range v {
|
||||||
if val == nil {
|
if val == nil {
|
||||||
@ -93,7 +93,7 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
parts[i] = fmt.Sprintf("%v", val)
|
parts[i] = fmt.Sprintf("%v", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
parts = []string{}
|
parts = []string{}
|
||||||
} else {
|
} else {
|
||||||
|
277
modules/http.lua
277
modules/http.lua
@ -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
|
|
40
moonshark.go
40
moonshark.go
@ -1,12 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"Moonshark/functions"
|
||||||
"Moonshark/modules"
|
"Moonshark/modules"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
)
|
)
|
||||||
@ -74,43 +73,12 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the bytecode so our Go module implementations can use it elsewhere
|
||||||
|
functions.SetStoredBytecode(bytecode)
|
||||||
|
|
||||||
// Execute the compiled bytecode
|
// Execute the compiled bytecode
|
||||||
if err := state.LoadAndRunBytecode(bytecode, scriptPath); err != nil {
|
if err := state.LoadAndRunBytecode(bytecode, scriptPath); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error executing '%s': %v\n", scriptPath, err)
|
fmt.Fprintf(os.Stderr, "Error executing '%s': %v\n", scriptPath, err)
|
||||||
os.Exit(1)
|
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user