moved code for coherence
This commit is contained in:
73
src/lua_func/handle.lua
Normal file
73
src/lua_func/handle.lua
Normal file
@@ -0,0 +1,73 @@
|
||||
local utils = require("utils")
|
||||
local tm = require("tm")
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local handle = {}
|
||||
|
||||
local function _get_func_by_path(file_path, func_name)
|
||||
-- 1. Load the file from the path
|
||||
-- loadfile returns a 'chunk' (a function that runs the file's code)
|
||||
local chunk, load_err = loadfile(file_path)
|
||||
|
||||
if not chunk then
|
||||
return nil, "Failed to load file: " .. tostring(load_err)
|
||||
end
|
||||
|
||||
-- 2. Execute the chunk to get the module's return value
|
||||
-- Most Lua modules end with 'return { ... }'
|
||||
local ok, module = pcall(chunk)
|
||||
|
||||
if not ok then
|
||||
return nil, "Error executing file: " .. tostring(module)
|
||||
end
|
||||
|
||||
-- 3. Validate the module is a table and contains the function
|
||||
if type(module) ~= "table" then
|
||||
return nil, "Module did not return a table (returned " .. type(module) .. ")"
|
||||
end
|
||||
|
||||
local target_func = module[func_name]
|
||||
if type(target_func) ~= "function" then
|
||||
return nil, "Function '" .. func_name .. "' not found in '" .. file_path .. "'"
|
||||
end
|
||||
|
||||
return target_func
|
||||
end
|
||||
|
||||
function handle.func_call(params)
|
||||
local file = params.file
|
||||
local fname = params.fname
|
||||
local prms = params.params
|
||||
local res = nil
|
||||
local succ, ret
|
||||
|
||||
local pfile = file
|
||||
-- 1. modify the file path if it is relative
|
||||
if utils.is_relative_path(pfile) then
|
||||
local td = tm.gd("test_directory")
|
||||
pfile = utils.join_paths(td, file)
|
||||
end
|
||||
|
||||
-- 2. retrieve the function "fname"
|
||||
local func, err = _get_func_by_path(pfile, fname)
|
||||
|
||||
-- 3. Execute the function
|
||||
if err == nil then
|
||||
print(string.format("Function executed from '%s'", pfile))
|
||||
utils.log("func_call function found '%s', '%s'", file, fname)
|
||||
succ, ret = pcall(func, unpack(prms))
|
||||
utils.log("func_call returned '%s', '%s'", tostring(succ), tostring(ret))
|
||||
|
||||
if succ then
|
||||
res = ret
|
||||
else
|
||||
err = ret
|
||||
end
|
||||
end
|
||||
|
||||
-- 4. Returns result
|
||||
return res, err
|
||||
end
|
||||
|
||||
return handle
|
||||
130
src/lua_func/json-rpc.lua
Normal file
130
src/lua_func/json-rpc.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
local json = require("cjson")
|
||||
local utils = require("utils")
|
||||
|
||||
local JSONRPC = {}
|
||||
JSONRPC.__index = JSONRPC
|
||||
|
||||
function JSONRPC.new(sock)
|
||||
local self = setmetatable({}, JSONRPC)
|
||||
self.sock = sock -- Function to transmit string data to transport (TCP/Websocket)
|
||||
self.methods = {} -- Methods the server provides to the client
|
||||
self.pending = {} -- Requests sent to client waiting for response
|
||||
self.next_id = 1
|
||||
|
||||
self.sock:settimeout(0.2)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Register a method the client can call
|
||||
function JSONRPC:register(name, callback)
|
||||
self.methods[name] = callback
|
||||
end
|
||||
|
||||
--- Handle incoming raw data from the transport layer
|
||||
function JSONRPC:handle_message(raw_data)
|
||||
utils.log("received: '%s'", raw_data)
|
||||
local ok, msg = pcall(json.decode, raw_data)
|
||||
if not ok then return self:_send_error(nil, "received message parse error in lua server") end
|
||||
|
||||
-- 1. Check if it's a Response (has 'result' or 'error' and 'id')
|
||||
if (msg.id ~= nil) and (msg.result ~= nil or msg.error ~= nil) then
|
||||
self.pending[msg.id] = msg
|
||||
return
|
||||
end
|
||||
|
||||
-- 2. Check if it's a Request
|
||||
if msg.method then
|
||||
return self:_handle_request(msg)
|
||||
end
|
||||
end
|
||||
|
||||
--- INTERNAL: Handle requests from the client
|
||||
function JSONRPC:_handle_request(req)
|
||||
local method = self.methods[req.method]
|
||||
local ok, ret
|
||||
local res, err
|
||||
if not method then
|
||||
if req.id then self:_send_error(req.id, string.format("Method '%s' not registered in lua server")) end
|
||||
return
|
||||
end
|
||||
ok, ret, err = pcall(method, req.params)
|
||||
utils.log("Call returned ok='%s', ret='%s'", tostring(ok), tostring(ret))
|
||||
|
||||
-- Only send response if it's not a Notification (notifications have no ID)
|
||||
if req.id then
|
||||
if ok then
|
||||
res = ret
|
||||
if res == nil then
|
||||
self:_send_error(req.id, tostring(err))
|
||||
else
|
||||
self:_send({ jsonrpc = "2.0", result = { returned_value = res }, id = req.id })
|
||||
end
|
||||
else
|
||||
self:_send_error(req.id, tostring(err))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Call a method on the client
|
||||
function JSONRPC:call(method, params)
|
||||
local id = self.next_id
|
||||
self.next_id = self.next_id + 1
|
||||
|
||||
self:_send({
|
||||
jsonrpc = "2.0",
|
||||
method = method,
|
||||
params = params,
|
||||
id = id
|
||||
})
|
||||
|
||||
-- ---- Wait for response (re-entrant loop)
|
||||
while true do
|
||||
self:poll()
|
||||
|
||||
if self.pending[id] then
|
||||
local resp = self.pending[id]
|
||||
self.pending[id] = nil
|
||||
|
||||
if resp.error then
|
||||
error(resp.error.message)
|
||||
end
|
||||
return resp.result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function JSONRPC:_send(data)
|
||||
local j = json.encode(data)
|
||||
utils.log("sending: '%s'", j)
|
||||
return self.sock:send(j .. "\n")
|
||||
end
|
||||
|
||||
function JSONRPC:_send_error(id, message)
|
||||
self:_send({
|
||||
jsonrpc = "2.0",
|
||||
error = message,
|
||||
id = id
|
||||
})
|
||||
end
|
||||
|
||||
function JSONRPC:poll()
|
||||
local line, err = self.sock:receive("*l")
|
||||
|
||||
if line then
|
||||
self:handle_message(line)
|
||||
elseif err ~= "timeout" and err ~= nil then
|
||||
utils.log("Connection ended: %s", err)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function JSONRPC:loop()
|
||||
while true do
|
||||
if not self:poll() then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return JSONRPC
|
||||
102
src/lua_func/main.lua
Normal file
102
src/lua_func/main.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
-- =========================
|
||||
-- Options par défaut
|
||||
-- =========================
|
||||
local config = {
|
||||
host = "0.0.0.0",
|
||||
port = 9000,
|
||||
timeout = 60,
|
||||
verbose = false,
|
||||
}
|
||||
|
||||
local function usage()
|
||||
print([[
|
||||
Usage: lua lua_func [options]
|
||||
|
||||
Options:
|
||||
--host <ip> Adresse d'écoute (default: 0.0.0.0)
|
||||
--port <port> Port TCP (default: 9000)
|
||||
--timeout <sec> Timeout client en secondes (default: 60)
|
||||
--verbose Logs détaillés
|
||||
--help Affiche cette aide
|
||||
]])
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Parsing des arguments
|
||||
-- =========================
|
||||
local i = 1
|
||||
while i <= #arg do
|
||||
local a = arg[i]
|
||||
|
||||
if a == "--host" then
|
||||
i = i + 1
|
||||
config.host = arg[i]
|
||||
|
||||
elseif a == "--port" then
|
||||
i = i + 1
|
||||
config.port = tonumber(arg[i])
|
||||
|
||||
elseif a == "--timeout" then
|
||||
i = i + 1
|
||||
config.timeout = tonumber(arg[i])
|
||||
|
||||
elseif a == "--verbose" then
|
||||
config.verbose = true
|
||||
|
||||
elseif a == "--help" then
|
||||
usage()
|
||||
|
||||
else
|
||||
print("Unknown option:", a)
|
||||
usage()
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
local socket = require("socket")
|
||||
local JSONRPC = require("json-rpc") -- The module from the previous response
|
||||
local utils = require("utils")
|
||||
local tm = require("tm")
|
||||
local handle = require("handle")
|
||||
|
||||
utils.verbose = config.verbose
|
||||
|
||||
-- Create the master socket
|
||||
local server_sock = socket.tcp()
|
||||
|
||||
local ok, err = server_sock:bind(config.host, config.port)
|
||||
if not ok then
|
||||
utils.log("error : %s", err)
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
server_sock:listen(1)
|
||||
local ip, port = server_sock:getsockname()
|
||||
utils.log("listening on %s:%d for %.1f secs", ip, port, config.timeout)
|
||||
|
||||
server_sock:settimeout(config.timeout) -- Prevents hanging on dead connections
|
||||
|
||||
-- Main Server Loop
|
||||
local client_sock, err = server_sock:accept()
|
||||
if err then
|
||||
utils.log("connection failed: %s", err)
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
utils.log("Client connected")
|
||||
|
||||
-- Initialize the RPC instance for this specific connection
|
||||
local rpc = JSONRPC.new(client_sock)
|
||||
|
||||
tm._init_api(rpc)
|
||||
utils.setup_remote_print(rpc)
|
||||
|
||||
rpc:register("func_call", handle.func_call)
|
||||
|
||||
-- Communication Loop for this client
|
||||
rpc:loop()
|
||||
|
||||
client_sock:close()
|
||||
utils.log("Server stopped")
|
||||
29
src/lua_func/tm.lua
Normal file
29
src/lua_func/tm.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
local tm = {}
|
||||
|
||||
local SUPPORTED_API = {
|
||||
"gd",
|
||||
"setgd",
|
||||
"delgd",
|
||||
}
|
||||
|
||||
-- underlying function
|
||||
|
||||
function tm._init_api(rpc)
|
||||
tm._rpc = rpc
|
||||
|
||||
local function _api_request(fname, ...)
|
||||
local args = {...}
|
||||
return tm._rpc:call(fname, args)
|
||||
end
|
||||
|
||||
for _, fname in ipairs(SUPPORTED_API) do
|
||||
-- create a closure that calls common_handler with fname
|
||||
tm[fname] = function(...)
|
||||
return _api_request(fname, ...)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return tm
|
||||
71
src/lua_func/utils.lua
Normal file
71
src/lua_func/utils.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
local utils = {}
|
||||
|
||||
utils.verbose = false
|
||||
|
||||
function utils.log(fmt, ...)
|
||||
if utils.verbose then
|
||||
-- print("[lua_func server]", ...)
|
||||
io.stdout:write(string.format("[lua_func server] - " .. fmt .. "\n", ...))
|
||||
end
|
||||
end
|
||||
|
||||
utils.sep = package.config:sub(1,1)
|
||||
|
||||
function utils.join_paths(p1, p2)
|
||||
return p1 .. utils.sep .. p2
|
||||
end
|
||||
|
||||
function utils.is_absolute_path(path)
|
||||
if not path or path == "" then return false end
|
||||
|
||||
-- 1. Check for POSIX absolute path (starts with /)
|
||||
if string.sub(path, 1, 1) == "/" then
|
||||
return true
|
||||
end
|
||||
|
||||
-- 2. Check for Windows drive letter (e.g., C:\ or D:/)
|
||||
-- Pattern: %a (letter) followed by : (colon)
|
||||
if string.match(path, "^%a:[/\\]") or string.match(path, "^%a:$") then
|
||||
return true
|
||||
end
|
||||
|
||||
-- 3. Check for Windows UNC/Network paths (starts with \\ or //)
|
||||
if string.match(path, "^[/\\][/\\]") then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function utils.is_relative_path(path)
|
||||
return not utils.is_absolute_path(path)
|
||||
end
|
||||
|
||||
function utils.setup_remote_print(rpc)
|
||||
-- Store the original print if you still need to log to the server console
|
||||
_G.native_print = _G.native_print or _G.print
|
||||
|
||||
-- Define the new local print
|
||||
_G.print = function (...)
|
||||
|
||||
local args = {...}
|
||||
local output = {}
|
||||
|
||||
for i, v in ipairs(args) do
|
||||
table.insert(output, tostring(v))
|
||||
end
|
||||
|
||||
local message = table.concat(output, "\t")
|
||||
utils.log("Printed value: '%s'", message)
|
||||
if string.sub(message, -1) ~= "\n" then
|
||||
message = message .. "\n"
|
||||
end
|
||||
pcall(function()
|
||||
rpc:call("print", message )
|
||||
end)
|
||||
-- Optional: Still print to the server's local console
|
||||
-- utils.log("[Remote Log Sent]: " .. message)
|
||||
end
|
||||
end
|
||||
|
||||
return utils
|
||||
Reference in New Issue
Block a user