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.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 5 of 5.
- It's the details...
2005-02-18 04:37:32 Bas-i [Reply]
The article does fine at showing basic rpc issues. There are numerous cpan modules as well as other articles/books on this (I think Programming Perl?).
They always forget the really difficult stuff such as handling multiple concurrent requests, preforking, perl's handling of signals during socket operations etc. etc..
So hit your own fabricated rpc server really hard and it dies on you so fast you won't believe it ;(
I just started using POE for servers like this, POE handles those details for me :) Fabricating something rpc-like as presented in this article is a no-brainer a*and* you get stability, performance and safe handling of signals for free (well, sort of).
In short: middleware is tough, don't build it yourself.
- rpc example
2005-02-06 18:00:18 trwww [Reply]
I use RPC in web browsers to send data to a server without submitting forms. See http://waveright.homeip.net/products/FavoritesAnywhere.html for a simple example.
- better to use CPAN
2005-02-04 12:43:20 perrin [Reply]
There are a number of RPC modules on CPAN which are better than this one. They handle concurrent requests, do more error-checking, have security features, etc. I would not recommend writing your own RPC from scratch like this. It's just asking for security and scalability problems.- better to use CPAN
2005-02-04 13:09:55 chromatic1 [Reply]
I read the article as a demonstration of how an RPC mechanism works instead of the best module to do it. At its heart, RPC isn't as complex as it seems. Doing it well takes some work, but the core requires only putting together a few pieces.- better to use CPAN
2005-02-04 15:03:47 perrin [Reply]
That's totally reasonable, but I think this article really downplays the deficiencies of the implementation shown, compared to the ones on CPAN. The one in the Advanced Perl Book is more complete, and it still has lots of warnings and disclaimers with it.
- better to use CPAN
- better to use CPAN



