EasyServe: a framework for starting tcp/unix services and connected
clients under one parent process and on remote hosts.
https://github.com/vjoel/easy-serve
gem install easy-serve
Here’s an example that computes (5+6+7)*120 using two service processes
and a remote client (optionally over ssh tunnel):
=============================================
require ‘easy-serve/remote’
tunnel = ARGV.delete("–tunnel")
address_there = ARGV.shift
unless address_there
abort <<-END
Usage: #$0 address_there [--tunnel]
The 'address_there' is the remote address on which client code will
run. It must be a destination accepted by ssh, optionally including
a user name:
[[email protected]]hostname
The 'hostname' may by any valid hostname or ssh alias.
If --tunnel is specified, use the ssh connection to tunnel the data
traffic. Otherwise, just use tcp. (Always use ssh to start the
remote process.)
END
end
EasyServe.start do |ez|
log = ez.log
log.level = Logger::INFO
log.formatter = nil if $VERBOSE
ez.start_services do
host = tunnel ? “localhost” : nil
# no need to expose port if tunnelled
ez.service "adder", :tcp, bind_host: host do |svr|
Thread.new do
loop do
Thread.new(svr.accept) do |conn|
begin
log.info "accepted connection from #{conn.inspect}"
sum = 0
while input = conn.gets and not input.empty?
log.info "read input: #{input.inspect}"
begin
sum += Integer(input)
rescue
log.error "bad input: #{input}"
raise
end
end
conn.puts sum
log.info "wrote sum: #{sum}"
ensure
conn.close
end
end
end
end
end
ez.service "multiplier", :tcp, bind_host: host do |svr|
Thread.new do
loop do
Thread.new(svr.accept) do |conn|
begin
log.info "accepted connection from #{conn.inspect}"
prod = 1
while input = conn.gets and not input.empty?
log.info "read input: #{input.inspect}"
begin
prod *= Integer(input)
rescue
log.error "bad input: #{input}"
raise
end
end
conn.puts prod
log.info "wrote product: #{prod}"
ensure
conn.close
end
end
end
end
end
end
ez.remote “adder”, “multiplier”,
host: address_there, tunnel: tunnel, log: true, eval: %{
log.progname = “client on #{host}”
adder, multiplier = conns
adder.puts 5
adder.puts 6
adder.puts 7
adder.close_write
log.info "reading from \#{adder.inspect}"
sum = Integer(adder.read)
log.info "sum = \#{sum}"
adder.close
multiplier.puts sum
multiplier.puts 10
multiplier.close_write
log.info "reading from \#{multiplier.inspect}"
prod = Integer(multiplier.read)
log.info "prod = \#{prod}"
multiplier.close
}
Note use of #{} to interpolate variables that are only available
in the binding where the code is eval-ed. Alternately, use
eval: %Q{…}
but then interpolation from this script is not possible.
end