Compare commits

..

4 Commits

Author SHA1 Message Date
9a274250cd update annotations 2025-08-01 23:25:46 +03:00
6d49d83ea7 update List.lua 2025-08-01 23:25:36 +03:00
fb04b3bc46 change in/out to request/response 2025-08-01 23:25:24 +03:00
a60b75a4c0 make lua deps modular 2025-08-01 23:18:45 +03:00
3 changed files with 268 additions and 224 deletions

View File

@@ -1,7 +1,9 @@
-- com/List.lua -- com/List.lua
if In.Params and In.Params.about then local session = require("session")
Out.Result = {
if session.request and session.request.params and session.request.params.about then
session.response.result = {
description = "Returns a list of available methods", description = "Returns a list of available methods",
params = { params = {
layer = "select which layer list to display" layer = "select which layer list to display"
@@ -46,8 +48,8 @@ local function scanDirectory(basePath, targetPath)
end end
local basePath = "com" local basePath = "com"
local layer = In.Params and In.Params.layer and In.Params.layer:gsub(">", "/") or nil local layer = session.request and session.request.params.layer and session.request.params.layer:gsub(">", "/") or nil
Out.Result = { session.response.result = {
answer = layer and scanDirectory(basePath, layer) or scanDirectory(basePath, "") answer = layer and scanDirectory(basePath, layer) or scanDirectory(basePath, "")
} }

View File

@@ -1,59 +1,47 @@
---@diagnostic disable: missing-fields, missing-return --@diagnostic disable: missing-fields, missing-return
---@alias AnyTable table<string, any>
---@type AnyTable ---@alias Any any
In = { ---@alias AnyTable table<string, Any>
Params = {},
}
---@type AnyTable --- Global session module interface
Out = { ---@class SessionModule
Result = {}, ---@field request AnyTable Input context (read-only)
} ---@field request.params AnyTable Request parameters
---@field response AnyTable Output context (write results/errors)
---@field response.result Any|string? Result payload (table or primitive)
---@field response.error { code: integer, message: string }? Optional error info
---@class Log --- Global log module interface
---@field Info fun(msg: string) ---@class LogModule
---@field Debug fun(msg: string) ---@field info fun(msg: string) Log informational message
---@field Error fun(msg: string) ---@field debug fun(msg: string) Log debug message
---@field Warn fun(msg: string) ---@field error fun(msg: string) Log error message
---@field Event fun(msg: string) ---@field warn fun(msg: string) Log warning message
---@field EventError fun(msg: string) ---@field event fun(msg: string) Log event (generic)
---@field EventWarn fun(msg: string) ---@field event_error fun(msg: string) Log event error
---@field event_warn fun(msg: string) Log event warning
---@type Log
Log = {}
--- Global net module interface
---@class HttpResponse ---@class HttpResponse
---@field status integer HTTP status code ---@field status integer HTTP status code
---@field status_text string HTTP status text ---@field status_text string HTTP status text
---@field body string Response body ---@field body string Response body
---@field content_length integer Content length ---@field content_length integer Content length
---@field headers table<string, string|string[]> Response headers ---@field headers AnyTable Map of headers
---@class Http ---@class HttpModule
---@field Get fun(log: boolean, url: string): HttpResponse, string? Makes HTTP GET request ---@field get fun(log: boolean, url: string): HttpResponse, string? Perform GET
---@field Post fun(log: boolean, url: string, content_type: string, payload: string): HttpResponse, string? Makes HTTP POST request ---@field post fun(log: boolean, url: string, content_type: string, payload: string): HttpResponse, string? Perform POST
---@class Net ---@class NetModule
---@field Http Http HTTP client methods ---@field http HttpModule HTTP client functions
---@type Net --- Exposed globals
Net = { ---@type SessionModule
Http = { session = session or {}
---Makes HTTP GET request
---@param log boolean Whether to log the request
---@param url string URL to request
---@return HttpResponse response
---@return string? error
Get = function(log, url) end,
---Makes HTTP POST request ---@type LogModule
---@param log boolean Whether to log the request log = log or {}
---@param url string URL to request
---@param content_type string Content-Type header ---@type NetModule
---@param payload string Request body net = net or {}
---@return HttpResponse response
---@return string? error
Post = function(log, url, content_type, payload) end
}
}

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
"math/rand/v2"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@@ -40,202 +41,260 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
L := lua.NewState() L := lua.NewState()
defer L.Close() defer L.Close()
inTable := L.NewTable() seed := rand.Int()
paramsTable := L.NewTable()
if fetchedParams, ok := req.Params.(map[string]any); ok { loadSessionMod := func(lL *lua.LState) int {
for k, v := range fetchedParams { h.x.SLog.Debug("import module session", slog.String("script", path))
L.SetField(paramsTable, k, ConvertGolangTypesToLua(L, v)) sessionMod := lL.NewTable()
inTable := lL.NewTable()
paramsTable := lL.NewTable()
if fetchedParams, ok := req.Params.(map[string]any); ok {
for k, v := range fetchedParams {
lL.SetField(paramsTable, k, ConvertGolangTypesToLua(lL, v))
}
} }
} lL.SetField(inTable, "params", paramsTable)
L.SetField(inTable, "Params", paramsTable)
L.SetGlobal("In", inTable)
outTable := L.NewTable() outTable := lL.NewTable()
resultTable := L.NewTable() resultTable := lL.NewTable()
L.SetField(outTable, "Result", resultTable) lL.SetField(outTable, "result", resultTable)
L.SetGlobal("Out", outTable)
logTable := L.NewTable() lL.SetField(sessionMod, "request", inTable)
lL.SetField(sessionMod, "response", outTable)
logFuncs := map[string]func(string, ...any){ lL.Push(sessionMod)
"Info": h.x.SLog.Info, lL.SetField(sessionMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
"Debug": h.x.SLog.Debug, return 1
"Error": h.x.SLog.Error,
"Warn": h.x.SLog.Warn,
} }
for name, logFunc := range logFuncs { loadLogMod := func(lL *lua.LState) int {
L.SetField(logTable, name, L.NewFunction(func(L *lua.LState) int { h.x.SLog.Debug("import module log", slog.String("script", path))
msg := L.ToString(1) logMod := lL.NewTable()
logFunc(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
logFuncs := map[string]func(string, ...any){
"info": h.x.SLog.Info,
"debug": h.x.SLog.Debug,
"error": h.x.SLog.Error,
"warn": h.x.SLog.Warn,
}
for name, logFunc := range logFuncs {
fun := logFunc
lL.SetField(logMod, name, lL.NewFunction(func(lL *lua.LState) int {
msg := lL.ToString(1)
fun(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
return 0
}))
}
lL.SetField(logMod, "event", lL.NewFunction(func(lL *lua.LState) int {
msg := lL.ToString(1)
h.x.Log.Printf("%s: %s", path, msg)
return 0 return 0
})) }))
lL.SetField(logMod, "event_error", lL.NewFunction(func(lL *lua.LState) int {
msg := lL.ToString(1)
h.x.Log.Printf("%s: %s: %s", colors.PrintError(), path, msg)
return 0
}))
lL.SetField(logMod, "event_warn", lL.NewFunction(func(lL *lua.LState) int {
msg := lL.ToString(1)
h.x.Log.Printf("%s: %s: %s", colors.PrintWarn(), path, msg)
return 0
}))
lL.Push(logMod)
lL.SetField(logMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
return 1
} }
L.SetField(logTable, "Event", L.NewFunction(func(L *lua.LState) int { loadNetMod := func(lL *lua.LState) int {
msg := L.ToString(1) h.x.SLog.Debug("import module net", slog.String("script", path))
h.x.Log.Printf("%s: %s", path, msg) netMod := lL.NewTable()
return 0 netModhttp := lL.NewTable()
}))
L.SetField(logTable, "EventError", L.NewFunction(func(L *lua.LState) int { lL.SetField(netModhttp, "get_request", lL.NewFunction(func(lL *lua.LState) int {
msg := L.ToString(1) logRequest := lL.ToBool(1)
h.x.Log.Printf("%s: %s: %s", colors.PrintError(), path, msg) url := lL.ToString(2)
return 0
}))
L.SetField(logTable, "EventWarn", L.NewFunction(func(L *lua.LState) int { req, err := http.NewRequest("GET", url, nil)
msg := L.ToString(1) if err != nil {
h.x.Log.Printf("%s: %s: %s", colors.PrintWarn(), path, msg) lL.Push(lua.LNil)
return 0 lL.Push(lua.LString(err.Error()))
})) return 2
}
L.SetGlobal("Log", logTable) addInitiatorHeaders(sid, r, req.Header)
net := L.NewTable() client := &http.Client{}
netHttp := L.NewTable() resp, err := client.Do(req)
if err != nil {
lL.Push(lua.LNil)
lL.Push(lua.LString(err.Error()))
return 2
}
defer resp.Body.Close()
L.SetField(netHttp, "Get", L.NewFunction(func(L *lua.LState) int { body, err := io.ReadAll(resp.Body)
logRequest := L.ToBool(1) if err != nil {
url := L.ToString(2) lL.Push(lua.LNil)
lL.Push(lua.LString(err.Error()))
return 2
}
req, err := http.NewRequest("GET", url, nil) if logRequest {
if err != nil { h.x.SLog.Info("HTTP GET request",
L.Push(lua.LNil) slog.String("script", path),
L.Push(lua.LString(err.Error())) slog.String("url", url),
return 2 slog.Int("status", resp.StatusCode),
} slog.String("status_text", resp.Status),
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
)
}
addInitiatorHeaders(sid, r, req.Header) result := lL.NewTable()
lL.SetField(result, "status", lua.LNumber(resp.StatusCode))
lL.SetField(result, "status_text", lua.LString(resp.Status))
lL.SetField(result, "body", lua.LString(body))
lL.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
client := &http.Client{} headers := lL.NewTable()
resp, err := client.Do(req) for k, v := range resp.Header {
if err != nil { lL.SetField(headers, k, ConvertGolangTypesToLua(lL, v))
L.Push(lua.LNil) }
L.Push(lua.LString(err.Error())) lL.SetField(result, "headers", headers)
return 2
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) lL.Push(result)
if err != nil { return 1
L.Push(lua.LNil) }))
L.Push(lua.LString(err.Error()))
return 2
}
if logRequest { lL.SetField(netModhttp, "post_request", lL.NewFunction(func(lL *lua.LState) int {
h.x.SLog.Info("HTTP GET request", logRequest := lL.ToBool(1)
slog.String("script", path), url := lL.ToString(2)
slog.String("url", url), contentType := lL.ToString(3)
slog.Int("status", resp.StatusCode), payload := lL.ToString(4)
slog.String("status_text", resp.Status),
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
)
}
result := L.NewTable() body := strings.NewReader(payload)
L.SetField(result, "status", lua.LNumber(resp.StatusCode))
L.SetField(result, "status_text", lua.LString(resp.Status))
L.SetField(result, "body", lua.LString(body))
L.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
headers := L.NewTable() req, err := http.NewRequest("POST", url, body)
for k, v := range resp.Header { if err != nil {
L.SetField(headers, k, ConvertGolangTypesToLua(L, v)) lL.Push(lua.LNil)
} lL.Push(lua.LString(err.Error()))
L.SetField(result, "headers", headers) return 2
}
L.Push(result) req.Header.Set("Content-Type", contentType)
addInitiatorHeaders(sid, r, req.Header)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
lL.Push(lua.LNil)
lL.Push(lua.LString(err.Error()))
return 2
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
lL.Push(lua.LNil)
lL.Push(lua.LString(err.Error()))
return 2
}
if logRequest {
h.x.SLog.Info("HTTP POST request",
slog.String("script", path),
slog.String("url", url),
slog.String("content_type", contentType),
slog.Int("status", resp.StatusCode),
slog.String("status_text", resp.Status),
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
)
}
result := lL.NewTable()
lL.SetField(result, "status", lua.LNumber(resp.StatusCode))
lL.SetField(result, "status_text", lua.LString(resp.Status))
lL.SetField(result, "body", lua.LString(respBody))
lL.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
headers := lL.NewTable()
for k, v := range resp.Header {
lL.SetField(headers, k, ConvertGolangTypesToLua(lL, v))
}
lL.SetField(result, "headers", headers)
lL.Push(result)
return 1
}))
lL.SetField(netMod, "http", netModhttp)
lL.Push(netMod)
lL.SetField(netMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
return 1 return 1
})) }
L.SetField(netHttp, "Post", L.NewFunction(func(L *lua.LState) int { L.PreloadModule("session", loadSessionMod)
logRequest := L.ToBool(1) L.PreloadModule("log", loadLogMod)
url := L.ToString(2) L.PreloadModule("net", loadNetMod)
contentType := L.ToString(3)
payload := L.ToString(4)
body := strings.NewReader(payload)
req, err := http.NewRequest("POST", url, body)
if err != nil {
L.Push(lua.LNil)
L.Push(lua.LString(err.Error()))
return 2
}
req.Header.Set("Content-Type", contentType)
addInitiatorHeaders(sid, r, req.Header)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
L.Push(lua.LNil)
L.Push(lua.LString(err.Error()))
return 2
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
L.Push(lua.LNil)
L.Push(lua.LString(err.Error()))
return 2
}
if logRequest {
h.x.SLog.Info("HTTP POST request",
slog.String("script", path),
slog.String("url", url),
slog.String("content_type", contentType),
slog.Int("status", resp.StatusCode),
slog.String("status_text", resp.Status),
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
)
}
result := L.NewTable()
L.SetField(result, "status", lua.LNumber(resp.StatusCode))
L.SetField(result, "status_text", lua.LString(resp.Status))
L.SetField(result, "body", lua.LString(respBody))
L.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
headers := L.NewTable()
for k, v := range resp.Header {
L.SetField(headers, k, ConvertGolangTypesToLua(L, v))
}
L.SetField(result, "headers", headers)
L.Push(result)
return 1
}))
L.SetField(net, "Http", netHttp)
L.SetGlobal("Net", net)
prep := filepath.Join(*h.x.Config.Conf.Node.ComDir, "_prepare.lua") prep := filepath.Join(*h.x.Config.Conf.Node.ComDir, "_prepare.lua")
if _, err := os.Stat(prep); err == nil { if _, err := os.Stat(prep); err == nil {
if err := L.DoFile(prep); err != nil { if err := L.DoFile(prep); err != nil {
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID) h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
} }
} }
if err := L.DoFile(path); err != nil { if err := L.DoFile(path); err != nil {
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID) h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
} }
lv := L.GetGlobal("Out") pkg := L.GetGlobal("package")
outTbl, ok := lv.(*lua.LTable) pkgTbl, ok := pkg.(*lua.LTable)
if !ok { if !ok {
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID) h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", "package not found"))
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
} }
// Check if Out.Error exists loaded := pkgTbl.RawGetString("loaded")
if errVal := outTbl.RawGetString("Error"); errVal != lua.LNil { loadedTbl, ok := loaded.(*lua.LTable)
if !ok {
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", "package.loaded not found"))
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
}
sessionVal := loadedTbl.RawGetString("session")
sessionTbl, ok := sessionVal.(*lua.LTable)
if !ok {
return rpc.NewResponse(map[string]any{
"responsible-node": h.cs.UUID32,
}, req.ID)
}
tag := sessionTbl.RawGetString("__gosally_internal")
if tag.Type() != lua.LTString || tag.String() != fmt.Sprint(seed) {
return rpc.NewResponse(map[string]any{
"responsible-node": h.cs.UUID32,
}, req.ID)
}
outVal := sessionTbl.RawGetString("response")
outTbl, ok := outVal.(*lua.LTable)
if !ok {
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", "response is not a table"))
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
}
if errVal := outTbl.RawGetString("error"); errVal != lua.LNil {
if errTbl, ok := errVal.(*lua.LTable); ok { if errTbl, ok := errVal.(*lua.LTable); ok {
code := rpc.ErrInternalError code := rpc.ErrInternalError
message := "Internal script error" message := rpc.ErrInternalErrorS
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber { if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
code = int(c.(lua.LNumber)) code = int(c.(lua.LNumber))
} }
@@ -245,21 +304,16 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
h.x.SLog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message)) h.x.SLog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
return rpc.NewError(code, message, req.ID) return rpc.NewError(code, message, req.ID)
} }
return rpc.NewError(rpc.ErrInternalError, "Out.Error is not a table", req.ID) return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
} }
// Otherwise, parse Out.Result resultVal := outTbl.RawGetString("result")
resultVal := outTbl.RawGetString("Result") payload := make(map[string]any)
resultTbl, ok := resultVal.(*lua.LTable) if tbl, ok := resultVal.(*lua.LTable); ok {
if !ok { tbl.ForEach(func(k, v lua.LValue) { payload[k.String()] = ConvertLuaTypesToGolang(v) })
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID) } else {
payload["message"] = ConvertLuaTypesToGolang(resultVal)
} }
payload["responsible-node"] = h.cs.UUID32
out := make(map[string]any) return rpc.NewResponse(payload, req.ID)
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
out[key.String()] = ConvertLuaTypesToGolang(value)
})
out["responsible-node"] = h.cs.UUID32
return rpc.NewResponse(out, req.ID)
} }