A while ago I wrote about a framework I had in mind for accessing
distributed objects in a language independent way, using tuples spaces
and xml marshalling.

I looked into XML-RPC for the same purpose, but it appeared that the
procedure call was being made the HTTP/1.0 way, i.e. one connection
for each call.

Since what started the whole thing was the idea of writing remote GUIs
in arbitrary languages, and since GUIs need prompt feedback, the need
to establish a new one for each call was definitely overkill, so I
discarded XML-RPC.

(As Horacio pointed out, HTTP/1.1 supports persistent connections and
indeed takes them for granted where not differently specified.  Anyone
knows whether this is available in XML-RPC too?)

So far I've only written a tiny xml marshalling system, mostly made up
of pieces from XMarshal by Masaki Fukushima, and the following very
simple proxying mechanism (based on simpletuple.rb from drb by Seki
Masatoshi), on which I'd be grateful for comments and caveats.


require 'simpletuple'

class Sample
  def ping
    true
  end

  def hello
    "hello world"
  end

  def sum(a,b)
    a+b
  end
end


class RPCHandler      # lies in the same space as proxied objects
  def initialize(ts)
    @ts = ts
  end

  def run
    loop do
      name, method, args = @ts.in('rpc')  # continually monitors for rpc's
      args ||= []

      object = @ts.in(name)  # simulate rd by first in'ning and then out'ing 
      @ts.out(name, object)

      @ts.out(['rpc-result', name], object.send(method, *args))
    end
  end
end


class Proxy
  def initialize(object_name, tuplespace)
    @proxied_name = object_name    # objects are identified in tuple space by a name
    @ts = tuplespace
  end

  def method_missing(method_id, *args, &block)
    @ts.out('rpc', [@proxied_name, method_id.to_s, args])  # send out a method call 
    @ts.in(['rpc-result', @proxied_name])                  # get results
  end
end


require 'Lapidary/TestCase'

class Test < Lapidary::TestCase
  def setup
    @ts = SimpleTupleSpace.new
  end

  def testSpaceGetsTuple
    @ts.out('req', 'try')
    assert_equal 'try', @ts.in('req')
  end

  # just put and get an object in tuple space, and then call a method
  #
  def testTupleSimpleProxying   
    @ts.out('obj', Sample.new)

    object = @ts.in('obj')
    assert_equal true, object.ping
  end

  # put an object in tuple space, start the rpc handler, send multiple
  # calls by accessing the space directly and get results back by
  # accessing the space directly
  #
  def testDirectRPCwithoutParams  
    @ts.out('obj', Sample.new)
    Thread.new { RPCHandler.new(@ts).run }

    3.times {
      @ts.out('rpc', ['obj', 'ping'])
      assert_equal true, @ts.in(['rpc-result', 'obj'])
    }

    3.times {
      @ts.out('rpc', ['obj', 'hello'])
      assert_equal 'hello world', @ts.in(['rpc-result', 'obj'])
    }
  end

  # as above, but also provide arguments to the method
  #
  def testDirectRPCwithParams
    @ts.out('obj', Sample.new)
    Thread.new { RPCHandler.new(@ts).run }

    3.times {
      @ts.out('rpc', ['obj', 'sum', [3, 2]])
      assert_equal 5, @ts.in(['rpc-result', 'obj'])
    }
  end    

  # put an object in tuple space, start the rpc handler, proxy the
  # object, then access it transparently (i.e. never caring about the
  # tuple space)
  # 
  def testProxiedRPC
    @ts.out('obj', Sample.new)
    Thread.new { RPCHandler.new(@ts).run }

    p = Proxy.new('obj', @ts)

    3.times { assert_equal true, p.ping }

    3.times { assert_equal "hello world", p.hello }

    3.times { assert_equal 5, p.sum(3,2) }
  end
end


if $0 == __FILE__
  require 'Lapidary/UI/Console/TestRunner'
  Lapidary::UI::Console::TestRunner.run(Test)
end



Of course it is just a prototype.  In a real implementation, the Proxy
object would reside in the client (GUI) application, and would marshal
the method calls and forward them to a socket, then read the results
back from the socket, unmarshal them and forward them to the client.
The tuple space would sit behind a series of sockets, and the
RPCHandler object would reside in the same space as the proxied
objects, continually monitoring for calls.

I don't know whether the project will ever go beyond exploration
phase, since all at once I'm dealing with a lot of subjects I never
touched before in my programming experience (sockets, threads, tuple
spaces, xml...), but if anybody is interested in it or deems it of any
use, I'd be happy to hear suggestions and ideas (requests are as
welcome of course, but I'm probably less than ready to grant them. ;-) )

Massimiliano