зеркало из https://github.com/github/ernicorn.git
237 строки
6.2 KiB
Ruby
237 строки
6.2 KiB
Ruby
require 'bert'
|
|
require 'logger'
|
|
require 'ernicorn/version'
|
|
|
|
module Ernicorn
|
|
class << self
|
|
attr_accessor :mods, :current_mod, :log
|
|
attr_accessor :count, :virgin_procline
|
|
end
|
|
|
|
self.count = 0
|
|
self.virgin_procline = $0
|
|
self.mods = {}
|
|
self.current_mod = nil
|
|
self.log = Logger.new(STDOUT)
|
|
self.log.level = Logger::FATAL
|
|
|
|
# Record a module.
|
|
# +name+ is the module Symbol
|
|
# +block+ is the Block containing function definitions
|
|
#
|
|
# Returns nothing
|
|
def self.mod(name, block)
|
|
m = Mod.new(name)
|
|
self.current_mod = m
|
|
self.mods[name] = m
|
|
block.call
|
|
end
|
|
|
|
# Record a function.
|
|
# +name+ is the function Symbol
|
|
# +block+ is the Block to associate
|
|
#
|
|
# Returns nothing
|
|
def self.fun(name, block)
|
|
self.current_mod.fun(name, block)
|
|
end
|
|
|
|
# Expose all public methods in a Ruby module:
|
|
# +name+ is the ernie module Symbol
|
|
# +mixin+ is the ruby module whose public methods are exposed
|
|
#
|
|
# Returns nothing
|
|
def self.expose(name, mixin)
|
|
context = Object.new
|
|
context.extend mixin
|
|
mod(name, lambda {
|
|
mixin.public_instance_methods.each do |meth|
|
|
fun(meth.to_sym, context.method(meth))
|
|
end
|
|
})
|
|
if defined? mixin.dispatching
|
|
self.current_mod.dispatching = mixin.method(:dispatching)
|
|
end
|
|
context
|
|
end
|
|
|
|
# Set the logfile to given path.
|
|
# +file+ is the String path to the logfile
|
|
#
|
|
# Returns nothing
|
|
def self.logfile(file)
|
|
self.log = Logger.new(file)
|
|
end
|
|
|
|
# Set the log level.
|
|
# +level+ is the Logger level (Logger::WARN, etc)
|
|
#
|
|
# Returns nothing
|
|
def self.loglevel(level)
|
|
self.log.level = level
|
|
end
|
|
|
|
# Dispatch the request to the proper mod:fun.
|
|
# +mod+ is the module Symbol
|
|
# +fun+ is the function Symbol
|
|
# +args+ is the Array of arguments
|
|
#
|
|
# Returns the Ruby object response
|
|
def self.dispatch(mod, fun, args)
|
|
self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
|
|
self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
|
|
|
|
begin
|
|
self.mods[mod].dispatching and
|
|
self.mods[mod].dispatching.call([fun, *args])
|
|
rescue Object => e
|
|
self.log.fatal(e)
|
|
end
|
|
|
|
self.mods[mod].funs[fun].call(*args)
|
|
end
|
|
|
|
# Read the length header from the wire.
|
|
# +input+ is the IO from which to read
|
|
#
|
|
# Returns the size Integer if one was read
|
|
# Returns nil otherwise
|
|
def self.read_4(input)
|
|
raw = input.read(4)
|
|
return nil unless raw
|
|
raw.unpack('N').first
|
|
end
|
|
|
|
# Read a BERP from the wire and decode it to a Ruby object.
|
|
# +input+ is the IO from which to read
|
|
#
|
|
# Returns a Ruby object if one could be read
|
|
# Returns nil otherwise
|
|
def self.read_berp(input)
|
|
packet_size = self.read_4(input)
|
|
return nil unless packet_size
|
|
bert = input.read(packet_size)
|
|
BERT.decode(bert)
|
|
end
|
|
|
|
# Write the given Ruby object to the wire as a BERP.
|
|
# +output+ is the IO on which to write
|
|
# +ruby+ is the Ruby object to encode
|
|
#
|
|
# Returns nothing
|
|
def self.write_berp(output, ruby)
|
|
data = BERT.encode(ruby)
|
|
output.write([data.bytesize].pack("N"))
|
|
output.write(data)
|
|
end
|
|
|
|
# Start the processing loop.
|
|
#
|
|
# Loops forever
|
|
def self.start
|
|
self.procline('starting')
|
|
self.log.info("(#{Process.pid}) Starting") if self.log.level <= Logger::INFO
|
|
self.log.debug(self.mods.inspect) if self.log.level <= Logger::DEBUG
|
|
|
|
input = IO.new(3)
|
|
output = IO.new(4)
|
|
input.sync = true
|
|
output.sync = true
|
|
|
|
loop do
|
|
process(input, output)
|
|
end
|
|
end
|
|
|
|
# Processes a single BertRPC command.
|
|
#
|
|
# input - The IO to #read command BERP from.
|
|
# output - The IO to #write reply BERP to.
|
|
#
|
|
# Returns a [iruby, oruby] tuple of incoming and outgoing BERPs processed.
|
|
def self.process(input, output)
|
|
self.procline('waiting')
|
|
iruby = self.read_berp(input)
|
|
return nil if iruby.nil?
|
|
|
|
self.count += 1
|
|
|
|
if iruby.size == 4 && iruby[0] == :call
|
|
mod, fun, args = iruby[1..3]
|
|
self.procline("#{mod}:#{fun}(#{args})")
|
|
self.log.info("-> " + iruby.inspect) if self.log.level <= Logger::INFO
|
|
begin
|
|
res = self.dispatch(mod, fun, args)
|
|
oruby = t[:reply, res]
|
|
self.log.debug("<- " + oruby.inspect) if self.log.level <= Logger::DEBUG
|
|
write_berp(output, oruby)
|
|
rescue ServerError => e
|
|
oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
|
|
self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
|
|
self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
|
|
write_berp(output, oruby)
|
|
rescue Object => e
|
|
oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
|
|
self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
|
|
self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
|
|
write_berp(output, oruby)
|
|
end
|
|
elsif iruby.size == 4 && iruby[0] == :cast
|
|
mod, fun, args = iruby[1..3]
|
|
self.procline("#{mod}:#{fun}(#{args})")
|
|
self.log.info("-> " + [:cast, mod, fun, args].inspect) if self.log.level <= Logger::INFO
|
|
oruby = t[:noreply]
|
|
write_berp(output, oruby)
|
|
|
|
begin
|
|
self.dispatch(mod, fun, args)
|
|
rescue Object => e
|
|
# ignore
|
|
end
|
|
else
|
|
self.procline("invalid request")
|
|
self.log.error("-> " + iruby.inspect) if self.log.level <= Logger::ERROR
|
|
oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
|
|
self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
|
|
write_berp(output, oruby)
|
|
end
|
|
|
|
self.procline('waiting')
|
|
[iruby, oruby]
|
|
end
|
|
|
|
def self.procline(msg)
|
|
$0 = "ernicorn handler #{VERSION} - #{self.virgin_procline} - [#{self.count}] #{msg}"[0..180]
|
|
end
|
|
|
|
def self.version
|
|
VERSION
|
|
end
|
|
|
|
class ServerError < StandardError; end
|
|
|
|
class Ernicorn::Mod
|
|
attr_accessor :name, :funs, :dispatching
|
|
|
|
def initialize(name)
|
|
self.name = name
|
|
self.funs = {}
|
|
self.dispatching = nil
|
|
end
|
|
|
|
def fun(name, block)
|
|
raise TypeError, "block required" if block.nil?
|
|
self.funs[name] = block
|
|
end
|
|
end
|
|
end
|
|
# Root level calls
|
|
|
|
def logfile(name)
|
|
Ernicorn.logfile(name)
|
|
end
|
|
|
|
def loglevel(level)
|
|
Ernicorn.loglevel(level)
|
|
end
|