Proxying Objects That Can't Be Distributed






Proxying Objects That Can't Be Distributed

Credit: James Edward Gray II

Problem

You want to allow classes to connect to your DRb server, without giving the server access to the class definition. Perhaps you've given clients an API to implement, and you don't want to make everyone send you the source to their implementations just so they can connect to the server.

…OR…

You have some code that is tied to local resources: database connections, log files, or even just the closure aspect of Ruby's blocks. You want this code to interact with a DRb server, but it must be run locally.

…OR…

You want to send an object to a DRb server, perhaps as a parameter to a method; but you want the server to notice changes to that object as your local code modifies it.

Solution

Rather than sending an object to the server, you can ask DRb to send a proxy instead. When the server acts on the proxy, a description of the act will be sent across the network. The client end will actually perform the action. In effect, you've partially switched the roles of the client and the server.

You can set up a proxy in two simple steps. First, make sure your client code includes the following line before it interacts with any server objects:

	DRb.start_service # The client needs to be a DRb service too.

That's generally just a good habit to get into with DRb client code, because it allows DRb to magically support some constructs (like Ruby's blocks) by sending a proxy object when necessary. If you're intentionally trying to send a proxy, it becomes essential.

As long as your client is a DRb service of its own, you can proxy all objects made from a specific class or individual objects by including the DRbUndumped module:

	class MyLocalClass
	  include DRbUndumped # The magic line. All objects of this type are proxied.
	  # …
	end

	# … OR …

	my_local_object.extend DRbUndumped # Proxy just this object.

Discussion

Under normal circumstances, DRb is very simple. A method call is packaged up (using Marshal) as a target object, method name, and some arguments. The resulting object is sent over the wire to the server, where it's executed. The important thing to notice is that the server receives copies of the original arguments.

The server unmarshals the data, invokes the method, packages the result, and sends it back. Again, the result objects are copied to the client.

But that process doesn't always work. Perhaps the server needs to pass a code block into a method call. Ruby's blocks cannot be serialized. DRb notices this special case and sends a proxy object instead. As the server interacts with the proxy, the calls are bundled up and sent back to you, just as described above, so everything just works.

But DRb can't magically notice all cases where copying is harmful. That's why you need DRbUndumped. By extending an object with DRbUndumped, you can force DRb to send a proxy object instead of the real object, and ensure that your code stays local.

If all this sounds confusing, a simple example will probably clear it right up. Let's code up a trivial hello server:

	#!/usr/bin/ruby
	# hello_server.rb
	require 'drb'

	# a simple greeter class
	class HelloService
	  def hello(in_stream, out_stream)
	    out_stream.puts 'What is your name?'
	    name = in_stream.gets.strip
	    out_stream.puts "Hello #{name}."
	  end
	end

	# start up DRb with URI and object to share
	DRb.start_service('druby://localhost:61676', HelloService.new)
	DRb.thread.join # wait on DRb thread to exit…

Now we try connecting with a simple client:

	#!/usr/bin/ruby
	# hello_client.rb
	require 'drb'

	# fetch service object and ask it to greet us…
	hello_service = DRbObject.new_with_uri('druby://localhost:61676')
	hello_service.hello($stdin, $stdout)

Unfortunately, that yields an error message. Obviously, $stdin and $stdout are local resources that won't be available from the remote service. We need to pass them by proxy to get this working:

	#!/usr/bin/ruby
	# hello_client2.rb
	require 'drb'

	DRb.start_service # make sure client can serve proxy  
objects…
	# and request that the streams be proxied
	$stdin.extend DRbUndumped
	$stdout.extend DRbUndumped

	# fetch service object and ask it to greet us…
	hello_service = DRbObject.new_with_uri('druby://localhost:61676')
	hello_service.hello($stdin, $stdout)

With that client, DRb has remote access to the streams (through the proxy objects) and can read and write them as needed.

See Also



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows