Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Throwing Shapes
by Vladi Belperchinov-Shabanski | Pages: 1, 2, 3

One Wish

At this point everything works, but as usual, someone will want another feature. Suppose that the server and the client sides each had one wish.

The server side wish may be to have a built-in facility to find callable functions so as to build the function map can be built automatically.

Automatic map discovery has one major flaw which is that all functions in the current package are available to the client. This may not be always desirable. There are simple solutions to the problem. For example, all functions that need external visibility within a package could have a specific name prefix. A map discovery procedure can filter the list of all functions with this prefix and map those externally under the original names (without the prefix).

The following code finds all defined functions in the current namespace (the one that called r_map_discover()) and returns a hash with function-name keys and function-code-reference values:

  sub r_map_discover
  {
    my ( $package ) = caller(); # get the package name of the caller
    my $prefix = shift;         # optional prefix
    my %map;

    # disable check for symbolic references
    no strict 'refs';

    # loop over all entries in the caller package's namespace
    while( my ( $k, $v ) = each %{ $package . '::' } ) 
      {
      my $sym = $package . '::' . $k; # construct the full name of each symbol
      next unless $k =~ s/^$prefix//; # allow only entries starting with prefix
      my $r = *{ $sym }{ 'CODE' };    # take reference to the CODE in the glob
      next unless $r;  # reference is empty, no code under this name, skip
      $map{ $k } = $r; # reference points to CODE, assign it to the map
      }
    return %map;
  }

To make the use automatic discovery instead of a static function map, write:

  # function table, maps caller names to actual server subs, initially empty
  our %FUNC_MAP;

  # run the automatic discovery function
  %FUNC_MAP = r_map_discover();

Now %FUNC_MAP has all of the externally-visible functions in the current package (namespace). That means it's time to modify the names in the module to work with automatic discovery. Suppose the prefix is x_:

  sub x_power
  {
    ...
  }
  
  sub x_range
  {
    ...
  }

The server will discover only those functions:

%FUNC_MAP = r_map_discover( 'x_' );

and the client will continue to call functions under their usual names:

  my $r = r_call( 'power',  2,  8 );  # $r = 256
  my @a = r_call( 'range', 12, 18 );  # @a = ( 12, 13, 14, 15, 16, 17, 18 )

That's it for the server's wish. Now it's time to grant the client's wish.

Call remote functions transparently might be most important client wish, avoiding the use of r_call().

Perl allows the creation of anonymous function references. It's also possible to install that reference in a namespace under a real name. The result is a function created at run-time. If the function definition takes place in a specific lexical context, it will still have access to that context even when called later from outside that context. Those functions are closures and they are one way to avoid using r_call():

  sub r_define_subs
  {
    my ( $package ) = caller(); # get the package name of the caller
    for my $fn ( @_ )           # loop over the specified function names
      {
      my $sym = $package . '::' . $fn;    # construct the full symbol name
      no strict;                          # turn off symbolic refs check
      *$sym = sub { r_call( $fn, @_ ); }; # construct and tie the closure
      use strict;                         # turn the check back on
      }
  }
  
  # define/import 'range' and 'tree' functions in the current package
  r_define_subs( 'range', 'tree' );
  
  # now call them as they are normal functions
  my @a = range( 12, 18 );      # @a = ( 12 .. 18 )
  my %t = tree();               # returns data as reference

This approach hides the use of r_call() to only one place which the client doesn't see. Wish granted.

Limits

The biggest limitations of PerlRC relate to serialization.

First of all, both the client and server must have compatible serialization modules or versions. This is crucial! To avoid problems here, either you'll have to write your own serialization code or perform some kind of version check. If you perform this check, be sure to do it before sending a request and response, in plain text, without using serialization at all.

Another problem is in what data you can serialize in the argument or result containers. Holding references there to something outside the same container may pull in more data than you want, if your serialization follows references, or it may not pull in enough data if your serialization process is very simple. Also there is no way to serialize file handles, compiled code, or objects (which are not in the same container really). In some cases, serializing code and objects may be possible if the serialization modules supports such features (as do Storable and FreezeThaw), if you have the required class modules on both sides, and if you trust code on either side.

The documentation of the serialization modules explain further limitations and workarounds for both approaches.

Conclusion

There is a bit more work to do on PerlRC before using it in production, but if you need simple RPC or you need to tweak the way RPC deals with data or communication, you may have good experiences writing your own implementation instead fitting your application around readymade modules. I hope this text is a good starting point.