require "jsonrpc/tcpsock" JSONRPC = JSONRPC or {} local function result_call(cb, method, xid, ...) return cb(unpack(arg)) end local function notify_call(cb, rpc, notify, method, ...) return cb(rpc, unpack(arg)) end function JSONRPC.make_client(host, port, notify_methods) local rpc = {} local _next_reqid = 0 local request_cb = {} local _ct = current_threadinfo assert(_ct, "You need to MakeJSONRPCClient from a thread context; I do a non-blocking open.") local conn conn = TCP.make_client(host, port, function(newconn, err) -- new connection if newconn then log.print('Connected to '..host..':'..port) trigger_fsm(_ct, "JSONRPC_Connect", {rpc}) else conn = nil log.error("Can't connect to JSONRPC host "..host..':'..port..': '..err) trigger_fsm(_ct, "JSONRPC_Connect", {nil, err}) end end, function(c, line) -- receive line --print('received from server: '..line) local ok, req = pcall(json.decode,line) if not ok then log.error('invalid input from server: '..tostring(line)) return false end --print('JSONRPC: '..StrTable(req)) if(req[1] == "result") then local cb = request_cb[req[2]] if cb then request_cb[req[2]] = nil result_call(cb, unpack(req)) end elseif(req[1] == "notify") then local m = notify_methods[req[2]] if m then notify_call(m, rpc, unpack(req)) end elseif(req[1] == "error") then log.error('Error from JSONRPC server:'..StrTable(req)) if req[2] then local cb = request_cb[req[2]] if cb then request_cb[req[2]] = nil result_call(cb, nil, req[3]) end end else log.error("Unknown JSONRPC method "..req[1].." from "..host..':'..port) end end, function(c) -- disconnect conn = nil log.error("Disconnected from JSONRPC host "..host..':'..port) end) setmetatable(rpc, { __index = function(t, k, v) return function(self, ...) if k == 'notify' then -- no result necessary for notifications local reqstr = json.encode{k, unpack(arg)} conn:Send(reqstr..'\n') --print('sending JSON: '..reqstr) return end local _ct = current_threadinfo assert(self == rpc, "Wrong 'self' pointer") assert(_ct, "You can only use RPC functions from within a coroutine context.") assert(conn, "Attempt to use disconnected RPC connection") _next_reqid = _next_reqid + 1 local reqstr = json.encode{k, _next_reqid, unpack(arg)} conn:Send(reqstr..'\n') --print('sending JSON: '..reqstr) -- set up a timeout handler local t = Timer() t:SetTimeout(60000, function() log.error("JSONRPC request timed out:\n"..reqstr) t = nil trigger_fsm(_ct, "JSONRPC_Result", {nil, "timed out"}) end) -- set up a result handler request_cb[_next_reqid] = function(...) t:Kill() t = nil trigger_fsm(_ct, "JSONRPC_Result", arg) end -- wait for ze result return unpack(WaitForSingleTrigger(_ct, "JSONRPC_Result")) end end}) return unpack(WaitForSingleTrigger(_ct, "JSONRPC_Connect")) end