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