121 lines
3.1 KiB
Ruby
121 lines
3.1 KiB
Ruby
require 'ffi-rzmq'
|
|
require 'json'
|
|
require 'time' # for Time#iso8601
|
|
|
|
# $LOAD_PATH.unshift(File.expand_path("../lib", __dir__)) # <<< add this line
|
|
# require "package"
|
|
|
|
def start_daemon(endpoint)
|
|
# Clean up stale ipc file (bind will fail otherwise)
|
|
begin
|
|
path = ENDPOINT.sub("ipc://", "")
|
|
File.unlink(path) if File.exist?(path)
|
|
rescue => e
|
|
warn "Cleanup failed: #{e.message}"
|
|
end
|
|
|
|
ctx = ZMQ::Context.new
|
|
rep = ctx.socket(ZMQ::REP)
|
|
rc = rep.bind(endpoint)
|
|
abort "bind failed: #{ZMQ::Util.error_string}" unless rc == 0
|
|
puts "RPC server on #{endpoint}"
|
|
|
|
# Define RPC methods (use a local variable, not a constant)
|
|
rpc_methods = {
|
|
"echo" => ->(params) { params }, # returns whatever you send
|
|
"add" => ->(params) { Array(params).map(&:to_f).sum }, # add numbers
|
|
"time" => ->(_params) { Time.now.utc.iso8601 } # current time
|
|
}
|
|
|
|
ok = ->(id, result) { { "jsonrpc"=>"2.0", "id"=>id, "result"=>result }.to_json }
|
|
err = ->(id, code, msg, data=nil) do
|
|
body = { "jsonrpc"=>"2.0", "id"=>id, "error"=>{ "code"=>code, "message"=>msg } }
|
|
body["error"]["data"] = data unless data.nil?
|
|
body.to_json
|
|
end
|
|
|
|
shutdown = proc do
|
|
begin
|
|
rep.close
|
|
ensure
|
|
ctx.terminate
|
|
end
|
|
puts "\nbye"
|
|
begin
|
|
path = endpoint.sub("ipc://", "")
|
|
File.unlink(path) if File.exist?(path)
|
|
rescue; end
|
|
puts "\nbye"
|
|
exit
|
|
end
|
|
trap("INT") { shutdown.call }
|
|
trap("TERM") { shutdown.call }
|
|
|
|
loop do
|
|
# raw = ''
|
|
# next unless rep.recv_string(raw) == 0
|
|
# puts raw
|
|
|
|
raw = ''
|
|
rc = rep.recv_string(raw)
|
|
puts "[server] recv rc=#{rc} raw=#{raw.inspect}"
|
|
#next unless rc == 0
|
|
|
|
|
|
|
|
|
|
|
|
if rc == -1
|
|
errno = ZMQ::Util.errno
|
|
warn "[server] recv error #{errno}: #{ZMQ::Util.error_string}"
|
|
case errno
|
|
when ZMQ::EINTR
|
|
next # interrupted by signal; retry
|
|
when ZMQ::ETERM
|
|
break # context shutting down
|
|
when ZMQ::EFSM
|
|
# Socket in wrong state (likely previous send failed).
|
|
# Reset the REP socket.
|
|
rep.close
|
|
rep = ctx.socket(ZMQ::REP)
|
|
rep.setsockopt(ZMQ::LINGER, 0)
|
|
rep.bind(ENDPOINT)
|
|
next
|
|
else
|
|
sleep 0.01 # avoid hot loop on unexpected errors
|
|
next
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
begin
|
|
req = JSON.parse(raw)
|
|
id = req["id"]
|
|
meth = req["method"]
|
|
params = req["params"]
|
|
|
|
if req["jsonrpc"] != "2.0"
|
|
rep.send_string(err.call(id, -32600, "Invalid Request: jsonrpc must be '2.0'"))
|
|
next
|
|
end
|
|
|
|
handler = rpc_methods[meth]
|
|
unless handler
|
|
rep.send_string(err.call(id, -32601, "Method not found: #{meth}"))
|
|
next
|
|
end
|
|
|
|
result = handler.call(params)
|
|
rep.send_string(ok.call(id, result))
|
|
rescue JSON::ParserError => e
|
|
rep.send_string(err.call(nil, -32700, "Parse error", e.message))
|
|
rescue => e
|
|
rep.send_string(err.call(nil, -32000, "Server error", e.message))
|
|
end
|
|
end
|
|
end
|