File Coverage

blib/lib/RPC/XML/Server.pm
Criterion Covered Total %
statement 215 554 38.8
branch 43 228 18.8
condition 24 125 19.2
subroutine 35 52 67.3
pod 21 26 80.7
total 338 985 34.3


line stmt bran cond sub pod time code
1             ###############################################################################
2             #
3             # This file copyright (c) 2001-2014 Randy J. Ray, all rights reserved
4             #
5             # Copying and distribution are permitted under the terms of the Artistic
6             # License 2.0 (http://www.opensource.org/licenses/artistic-license-2.0.php) or
7             # the GNU LGPL (http://www.opensource.org/licenses/lgpl-2.1.php).
8             #
9             ###############################################################################
10             #
11             # Description: This class implements an RPC::XML server, using the core
12             # XML::RPC transaction code. The server may be created with
13             # or without an HTTP::Daemon object instance to answer the
14             # requests.
15             #
16             # Functions: new
17             # version
18             # url
19             # product_tokens
20             # started
21             # path
22             # host
23             # port
24             # requests
25             # response
26             # compress
27             # compress_thresh
28             # compress_re
29             # message_file_thresh
30             # message_temp_dir
31             # xpl_path
32             # add_method
33             # method_from_file
34             # get_method
35             # server_loop
36             # post_configure_hook
37             # pre_loop_hook
38             # process_request
39             # dispatch
40             # call
41             # add_default_methods
42             # add_methods_in_dir
43             # delete_method
44             # list_methods
45             # share_methods
46             # copy_methods
47             # timeout
48             # server_fault
49             #
50             # Libraries: HTTP::Daemon (conditionally)
51             # HTTP::Response
52             # HTTP::Status
53             # URI
54             # Scalar::Util
55             # RPC::XML
56             # RPC::XML::ParserFactory
57             # RPC::XML::Procedure
58             # Compress::Raw::Zlib is used if available
59             #
60             # Global Consts: $VERSION
61             # $INSTALL_DIR
62             # %FAULT_TABLE
63             #
64             ###############################################################################
65              
66             package RPC::XML::Server;
67              
68 8     8   172917 use 5.008008;
  8         18  
69 8     8   26 use strict;
  8         8  
  8         136  
70 8     8   26 use warnings;
  8         10  
  8         229  
71 8         619 use vars qw($VERSION $INSTALL_DIR %FAULT_TABLE @XPL_PATH %CLASS_MAP
72 8     8   29 $IO_SOCKET_SSL_HACK_NEEDED $COMPRESSION_AVAILABLE);
  8         7  
73              
74 8     8   26 use Carp qw(carp croak);
  8         10  
  8         369  
75 8     8   31 use File::Spec;
  8         7  
  8         122  
76 8     8   4336 use File::Temp;
  8         82531  
  8         530  
77 8     8   38 use IO::Handle;
  8         85  
  8         233  
78 8     8   1364 use Module::Load;
  8         2499  
  8         58  
79 8     8   326 use Scalar::Util 'blessed';
  8         10  
  8         273  
80              
81 8     8   1722 use HTTP::Status;
  8         10182  
  8         1500  
82 8     8   1567 use HTTP::Response;
  8         36883  
  8         159  
83 8     8   29 use URI;
  8         9  
  8         141  
84              
85 8     8   2773 use RPC::XML;
  8         13  
  8         325  
86 8     8   2833 use RPC::XML::ParserFactory;
  8         11  
  8         37  
87 8     8   3120 use RPC::XML::Procedure;
  8         16  
  8         807  
88              
89             BEGIN
90             {
91 8     8   257 $INSTALL_DIR =
92             File::Spec->catpath((File::Spec->splitpath(__FILE__))[0, 1], q{});
93 8         47 @XPL_PATH = ($INSTALL_DIR, File::Spec->curdir);
94              
95             # For now, I have an ugly hack in place to make the functionality that
96             # runs under HTTP::Daemon/Net::Server work better with SSL. This flag
97             # starts out true, then gets set to false the first time the hack is
98             # applied, so that it doesn't get repeated over and over...
99 8         10 $IO_SOCKET_SSL_HACK_NEEDED = 1;
100              
101             # Check for compression support
102             $COMPRESSION_AVAILABLE =
103 8 50       12 (eval { load Compress::Zlib; 1; }) ? 'deflate' : q{};
  8         27  
  8         280188  
104              
105             # Set up the initial table of fault-types and their codes/messages
106 8         55 %FAULT_TABLE = (
107             badxml => [ 100 => 'XML parse error: %s' ],
108             badmethod => [ 200 => 'Method lookup error: %s' ],
109             badsignature => [ 201 => 'Method signature error: %s' ],
110             execerror => [ 300 => 'Code execution error: %s' ],
111             );
112              
113             # This is used by add_method to map "types" to instantiation classes
114 8         6113 %CLASS_MAP = (
115             method => 'RPC::XML::Method',
116             procedure => 'RPC::XML::Procedure',
117             function => 'RPC::XML::Function',
118             );
119             }
120              
121             $VERSION = '1.73';
122             $VERSION = eval $VERSION; ## no critic (ProhibitStringyEval)
123              
124             ###############################################################################
125             #
126             # Sub Name: new
127             #
128             # Description: Create a new RPC::XML::Server object. This entails getting
129             # a HTTP::Daemon object, saving several internal values, and
130             # other operations.
131             #
132             # Arguments: NAME IN/OUT TYPE DESCRIPTION
133             # $class in scalar Ref or string for the class
134             # %args in hash Additional arguments
135             #
136             # Returns: Success: object reference
137             # Failure: error string
138             #
139             ###############################################################################
140             sub new ## no critic (ProhibitExcessComplexity)
141             {
142 5     5 1 15400 my ($class, %args) = @_;
143              
144             my (
145 5         6 $self, $http, $resp, $host, $port, $queue, $URI, $srv_version,
146             $srv_name
147             );
148              
149             # Don't accept a blessed value for $class
150 5 100       27 if (ref $class)
151             {
152 1         3 return __PACKAGE__ . '::new: Must be called as a static method';
153             }
154              
155 4         10 $self = bless {}, $class;
156              
157 4   33     28 $srv_version = delete $args{server_version} || $self->version;
158 4   33     20 $srv_name = delete $args{server_name} || $class;
159 4         49 $self->{__server_token} = "$srv_name/$srv_version";
160              
161 4 100       13 if (delete $args{no_http})
162             {
163 1   50     6 $self->{__host} = delete $args{host} || q{};
164 1   50     4 $self->{__port} = delete $args{port} || q{};
165             }
166             else
167             {
168 3         1326 require HTTP::Daemon;
169              
170 3   100     44491 $host = delete $args{host} || q{};
171 3   50     18 $port = delete $args{port} || q{};
172 3   50     15 $queue = delete $args{queue} || 5;
173 3 100       27 $http = HTTP::Daemon->new(
    50          
    50          
174             ReuseAddr => 1,
175             ($host ? (LocalHost => $host) : ()),
176             ($port ? (LocalPort => $port) : ()),
177             ($queue ? (Listen => $queue) : ())
178             );
179 3 50       1284 if (! $http)
180             {
181 0         0 return "${class}::new: Unable to create HTTP::Daemon object: $@";
182             }
183 3         11 $URI = URI->new($http->url);
184 3         17103 $self->{__host} = $URI->host;
185 3         274 $self->{__port} = $URI->port;
186 3         55 $self->{__daemon} = $http;
187             }
188              
189             # Create and store the cached response object for later cloning and use
190 4         34 $resp = HTTP::Response->new();
191             $resp->header(
192             # This is essentially the same string returned by the
193             # default "identity" method that may be loaded from a
194             # XPL file. But it hasn't been loaded yet, and may not
195             # be, hence we set it here (possibly from option values)
196             RPC_Server => $self->{__server_token},
197 4         242 RPC_Encoding => 'XML-RPC',
198             # Set any other headers as well
199             Accept => 'text/xml'
200             );
201 4         521 $resp->content_type('text/xml');
202 4         93 $resp->code(RC_OK);
203 4         30 $resp->message('OK');
204 4         23 $self->{__response} = $resp;
205              
206             # Basic (scalar) properties
207 4   50     24 $self->{__path} = delete $args{path} || q{};
208 4         6 $self->{__started} = 0;
209 4         10 $self->{__method_table} = {};
210 4         8 $self->{__requests} = 0;
211 4   50     21 $self->{__auto_methods} = delete $args{auto_methods} || 0;
212 4   50     18 $self->{__auto_updates} = delete $args{auto_updates} || 0;
213 4   50     19 $self->{__debug} = delete $args{debug} || 0;
214 4   50     18 $self->{__xpl_path} = delete $args{xpl_path} || [];
215 4   50     20 $self->{__timeout} = delete $args{timeout} || 10;
216             $self->{__parser} = RPC::XML::ParserFactory->new(
217 4 50       38 $args{parser} ? @{delete $args{parser}} : ()
  0         0  
218             );
219              
220             # Add the basic paths (content of @XPL_PATH) to our local XPL path
221 4         7 push @{$self->{__xpl_path}}, @XPL_PATH;
  4         17  
222              
223             # Set up the default methods unless requested not to
224 4 100       12 if (! delete $args{no_default})
225             {
226 1         3 $self->add_default_methods;
227             }
228              
229             # Compression support
230 3 50       7 if (delete $args{no_compress})
231             {
232 0         0 $self->{__compress} = q{};
233             }
234             else
235             {
236 3         8 $self->{__compress} = $COMPRESSION_AVAILABLE;
237             # Add some more headers to the default response object for compression.
238             # It looks wasteful to keep using the hash key, but it makes it easier
239             # to change the string in just one place (above) if I have to.
240 3 50       11 if ($self->{__compress})
241             {
242 3         19 $resp->header(Accept_Encoding => $self->{__compress});
243             }
244 3   50     120 $self->{__compress_thresh} = delete $args{compress_thresh} || 4096;
245             # Yes, I know this is redundant. It's for future expansion/flexibility.
246             $self->{__compress_re} =
247 3 50       44 $self->{__compress} ? qr/$self->{__compress}/ : qr/deflate/;
248             }
249              
250             # Parameters to control the point at which messages are shunted to temp
251             # files due to size, and where to home the temp files. Start with a size
252             # threshhold of 1Meg and no specific dir (which will fall-through to the
253             # tmpdir() method of File::Spec).
254             $self->{__message_file_thresh} = delete $args{message_file_thresh} ||
255 3   50     18 1_048_576;
256 3   50     13 $self->{__message_temp_dir} = delete $args{message_temp_dir} || q{};
257              
258             # Set up the table of response codes/messages that will be used when the
259             # server is sending a controlled error message to a client (as opposed to
260             # something HTTP-level that is less within our control).
261 3         5 $self->{__fault_table} = {};
262 3         11 for my $fault (keys %FAULT_TABLE)
263             {
264 12         11 $self->{__fault_table}->{$fault} = [ @{$FAULT_TABLE{$fault}} ];
  12         26  
265             }
266 3 50       10 if ($args{fault_code_base})
267             {
268 0         0 my $base = delete $args{fault_code_base};
269             # Apply the numerical offset to all (current) error codes
270 0         0 for my $key (keys %{$self->{__fault_table}})
  0         0  
271             {
272 0         0 $self->{__fault_table}->{$key}->[0] += $base;
273             }
274             }
275 3 50       7 if ($args{fault_table})
276             {
277 0         0 my $local_table = delete $args{fault_table};
278             # Merge any data from this table into the object's fault-table
279 0         0 for my $key (keys %{$local_table})
  0         0  
280             {
281             $self->{__fault_table}->{$key} = (ref $local_table->{$key}) ?
282 0 0       0 [ @{$local_table->{$key}} ] : $local_table->{$key};
  0         0  
283             }
284             }
285              
286             # Copy the remaining args over untouched
287 3         8 for (keys %args)
288             {
289 0         0 $self->{$_} = $args{$_};
290             }
291              
292 3         23 return $self;
293             }
294              
295             # Most of these tiny subs are accessors to the internal hash keys. They not
296             # only control access to the internals, they ease sub-classing.
297              
298 6     6 1 548 sub version { return $VERSION }
299              
300 1     1 1 4 sub INSTALL_DIR { return $INSTALL_DIR }
301              
302             sub url
303             {
304 1     1 1 3 my $self = shift;
305              
306 1         1 my $host;
307              
308 1 50       4 if ($self->{__daemon})
309             {
310 0         0 return $self->{__daemon}->url;
311             }
312 1 50       3 if (! ($host = $self->host))
313             {
314 1         3 return;
315             }
316              
317 0         0 my $path = $self->path;
318 0         0 my $port = $self->port;
319 0 0       0 if ($port == 443)
    0          
320             {
321 0         0 return "https://$host$path";
322             }
323             elsif ($port == 80)
324             {
325 0         0 return "http://$host$path";
326             }
327             else
328             {
329 0         0 return "http://$host:$port$path";
330             }
331             }
332              
333             sub product_tokens
334             {
335 1     1 1 2 my $self = shift;
336              
337 1         1 my $class = ref $self;
338 1   33     4 $class ||= $self;
339 1         2 return sprintf '%s/%s', $class, $self->version;
340             }
341              
342             # This fetches/sets the internal "started" timestamp. Unlike the other
343             # plain-but-mutable attributes, this isn't set to the passed-value but
344             # rather a non-null argument sets it from the current time.
345             sub started
346             {
347 2     2 1 8 my ($self, $set_started) = @_;
348              
349 2   50     26 my $old = $self->{__started} || 0;
350 2 100       9 if ($set_started)
351             {
352 1         9 $self->{__started} = time;
353             }
354              
355 2         9 return $old;
356             }
357              
358             BEGIN
359             {
360 8     8   47 no strict 'refs'; ## no critic (ProhibitNoStrict)
  8         9  
  8         930  
361              
362             # These are mutable member values for which the logic only differs in
363             # the name of the field to modify:
364 8     8   15 for my $method (qw(compress_thresh message_file_thresh message_temp_dir))
365             {
366 24         78 *{$method} = sub {
367 0     0   0 my ($self, $value) = @_;
368              
369 0         0 my $old = $self->{"__$method"};
370 0 0       0 if (defined $value)
371             {
372 0         0 $self->{"__$method"} = $value;
373             }
374              
375 0         0 $old;
376             }
377 24         84 }
378              
379             # These are immutable member values, so this simple block applies to all
380 8         19 for my $method (qw(path host port requests response compress compress_re
381             parser))
382             {
383 64     6   8801 *{$method} = sub { shift->{"__$method"} }
  6         414  
384 64         156 }
385             }
386              
387             # Get/set the search path for XPL files
388             sub xpl_path
389             {
390 1     1 1 2 my ($self, $path) = @_;
391 1         2 my $ret = $self->{__xpl_path};
392              
393 1 50 33     4 if ($path && ref $path eq 'ARRAY')
394             {
395 0         0 $self->{__xpl_path} = $path;
396             }
397              
398 1         2 return $ret;
399             }
400              
401             ###############################################################################
402             #
403             # Sub Name: add_method
404             #
405             # Description: Add a funtion-to-method mapping to the server object.
406             #
407             # Arguments: NAME IN/OUT TYPE DESCRIPTION
408             # $self in ref Object to add to
409             # $meth in scalar Hash ref of data or file name
410             #
411             # Globals: %CLASS_MAP
412             #
413             # Returns: Success: $self
414             # Failure: error string
415             #
416             ###############################################################################
417             sub add_method
418             {
419 3     3 1 1035 my ($self, $meth) = @_;
420              
421 3         9 my $me = ref($self) . '::add_method';
422              
423 3 100 0     13 if (! ref $meth)
    50          
    0          
424             {
425 1         4 my $val = $self->method_from_file($meth);
426 0 0       0 if (! ref $val)
427             {
428 0         0 return "$me: Error loading from file $meth: $val";
429             }
430             else
431             {
432 0         0 $meth = $val;
433             }
434             }
435             elsif (ref $meth eq 'HASH')
436             {
437             # Make a copy of the contents of $meth, so we don't make permanent
438             # changes:
439 2         2 my %meth_copy = map { $_ => $meth->{$_} } (keys %{$meth});
  6         12  
  2         4  
440              
441             # If the type of this method is not set, default to "method". The
442             # add_procedure and add_function calls should set this as needed.
443 2   50     10 my $type = delete $meth_copy{type} || 'method';
444              
445 2 50       8 if (! (my $class = $CLASS_MAP{lc $type}))
446             {
447 0         0 return "$me: Unknown type: $type";
448             }
449             else
450             {
451 2         20 $meth = $class->new(\%meth_copy);
452             }
453             }
454             elsif (! (blessed $meth and $meth->isa('RPC::XML::Procedure')))
455             {
456 0         0 return "$me: Method argument must be a file name, a hash " .
457             'reference or an object derived from RPC::XML::Procedure';
458             }
459              
460 2         10 $self->{__method_table}->{$meth->name} = $meth;
461              
462 2         4 return $self;
463             }
464              
465             ###############################################################################
466             #
467             # Sub Name: add_procedure
468             #
469             # Description: This filters through to add_method, but if the passed-in
470             # value is a hash reference forces the "type" to be
471             # "procedure".
472             #
473             # Arguments: NAME IN/OUT TYPE DESCRIPTION
474             # $self in ref Object reference
475             # $meth in scalar Procedure to add
476             #
477             # Returns: threads through to add_method
478             #
479             ###############################################################################
480             sub add_procedure
481             {
482 0     0 1 0 my ($self, $meth) = @_;
483              
484             # Anything else but a hash-reference goes through unaltered
485 0 0       0 if (ref($meth) eq 'HASH')
486             {
487 0         0 $meth->{type} = 'procedure';
488             }
489              
490 0         0 return $self->add_method($meth);
491             }
492              
493             ###############################################################################
494             #
495             # Sub Name: add_function
496             #
497             # Description: This filters through to add_method, but if the passed-in
498             # value is a hash reference forces the "type" to be
499             # "function".
500             #
501             # Arguments: NAME IN/OUT TYPE DESCRIPTION
502             # $self in ref Object reference
503             # $meth in scalar Procedure to add
504             #
505             # Returns: threads through to add_method
506             #
507             ###############################################################################
508             sub add_function
509             {
510 0     0 1 0 my ($self, $meth) = @_;
511              
512             # Anything else but a hash-reference goes through unaltered
513 0 0       0 if (ref($meth) eq 'HASH')
514             {
515 0         0 $meth->{type} = 'function';
516             }
517              
518 0         0 return $self->add_method($meth);
519             }
520              
521             ###############################################################################
522             #
523             # Sub Name: method_from_file
524             #
525             # Description: Create a RPC::XML::Procedure (or ::Method) object from the
526             # passed-in file name, using the object's search path if the
527             # name is not already absolute.
528             #
529             # Arguments: NAME IN/OUT TYPE DESCRIPTION
530             # $self in ref Object of this class
531             # $file in scalar Name of file to load
532             #
533             # Returns: Success: Method-object reference
534             # Failure: error message
535             #
536             ###############################################################################
537             sub method_from_file
538             {
539 2     2 0 386 my ($self, $file) = @_;
540              
541 2 100       22 if (! File::Spec->file_name_is_absolute($file))
542             {
543 1         1 my $path;
544 1         2 for my $dir (@{$self->xpl_path})
  1         3  
545             {
546 2         18 $path = File::Spec->catfile($dir, $file);
547 2 50       55 if (-f $path)
548             {
549 0         0 $file = File::Spec->canonpath($path);
550 0         0 last;
551             }
552             }
553             }
554             # Just in case it still didn't appear in the path, we really want an
555             # absolute path:
556 2 100       10 if (! File::Spec->file_name_is_absolute($file))
557             {
558 1         16 $file = File::Spec->rel2abs($file);
559             }
560              
561             # When reading a XPL file, RPC::XML::Procedure->new() acts sort of like a
562             # factory constructor, returning the type of object the XPL file specifies
563             # even when that isn't RPC::XML::Procedure.
564 2         18 return RPC::XML::Procedure->new($file);
565             }
566              
567             ###############################################################################
568             #
569             # Sub Name: get_method
570             #
571             # Description: Get the current binding for the remote-side method $name.
572             # Returns undef if the method is not defined for the server
573             # instance.
574             #
575             # Arguments: NAME IN/OUT TYPE DESCRIPTION
576             # $self in ref Class instance
577             # $name in scalar Name of the method being looked
578             # up
579             #
580             # Returns: Success: Method-class reference
581             # Failure: error string
582             #
583             ###############################################################################
584             sub get_method
585             {
586 0     0 1 0 my ($self, $name) = @_;
587              
588 0         0 my $meth = $self->{__method_table}->{$name};
589 0 0       0 if (! defined $meth)
590             {
591 0 0       0 if ($self->{__auto_methods})
592             {
593             # Try to load this dynamically on the fly, from any of the dirs
594             # that are in this object's @xpl_path
595 0         0 (my $loadname = $name) =~ s/^system[.]//;
596 0         0 $self->add_method("$loadname.xpl");
597             }
598             # If method is still not in the table, we were unable to load it
599 0 0       0 if (! ($meth = $self->{__method_table}->{$name}))
600             {
601 0         0 return "Unknown method: $name";
602             }
603             }
604             # Check the mod-time of the file the method came from, if the test is on
605 0 0 0     0 if ($self->{__auto_updates} &&
      0        
606             $meth->{file} &&
607             ($meth->{mtime} < (stat $meth->{file})[9]))
608             {
609 0         0 my $ret = $meth->reload;
610 0 0       0 if (! ref $ret)
611             {
612 0         0 return "Reload of method $name failed: $ret";
613             }
614             }
615              
616 0         0 return $meth;
617             }
618              
619             # For name-symmetry:
620             *get_procedure = *get_function = \&get_method;
621              
622             ###############################################################################
623             #
624             # Sub Name: server_loop
625             #
626             # Description: Enter a server-loop situation, using the accept() loop of
627             # HTTP::Daemon if $self has such an object, or falling back
628             # Net::Server otherwise.
629             #
630             # The critic disabling is because we may manipulate @_
631             # when using Net::Server.
632             #
633             # Arguments: NAME IN/OUT TYPE DESCRIPTION
634             # $self in ref Object of this class
635             # %args in hash Additional parameters to set up
636             # before calling the superclass
637             # Run method
638             #
639             # Returns: string if error, otherwise void
640             #
641             ###############################################################################
642             sub server_loop ## no critic (RequireArgUnpacking,ProhibitExcessComplexity)
643             {
644 1     1 1 1425 my $self = shift;
645              
646 1 50       31 if ($self->{__daemon})
647             {
648 1         8 my ($conn, $req, $resp, $reqxml, $respxml, $exit_now, $timeout,
649             $eval_return);
650              
651 1         28 my %args = @_;
652              
653             # Localize and set the signal handler as an exit route
654 1         7 my @exit_signals;
655              
656 1 50 33     24 if (exists $args{signal} and $args{signal} ne 'NONE')
657             {
658             @exit_signals =
659 0 0       0 (ref $args{signal}) ? @{$args{signal}} : $args{signal};
  0         0  
660             }
661             else
662             {
663 1         10 push @exit_signals, 'INT';
664             }
665              
666 1     0   89 local @SIG{@exit_signals} = (sub { $exit_now++ }) x @exit_signals;
  0         0  
667              
668 1         38 $self->started('set');
669 1         1 $exit_now = 0;
670 1         66 $timeout = $self->{__daemon}->timeout(1);
671 1         33 while (! $exit_now)
672             {
673 1         35 $conn = $self->{__daemon}->accept;
674              
675 1 50       2687 if ($exit_now)
676             {
677 0         0 last;
678             }
679 1 50       9 if (! $conn)
680             {
681 0         0 next;
682             }
683 1         14 $conn->timeout($self->timeout);
684 1         13 $self->process_request($conn);
685              
686 0         0 $eval_return = eval {
687 0     0   0 local $SIG{PIPE} = sub { die "server_loop: Caught SIGPIPE\n"; };
  0         0  
688 0         0 $conn->close;
689 0         0 1;
690             };
691 0 0 0     0 if ((! $eval_return) && $@)
692             {
693 0         0 warn "Cannot close connection: $@\n";
694             }
695              
696 0         0 undef $conn; # Free up any lingering resources
697             }
698              
699 0 0       0 if (defined $timeout)
700             {
701 0         0 $self->{__daemon}->timeout($timeout);
702             }
703             }
704             else
705             {
706             # This is the Net::Server block, but for now HTTP::Daemon is needed
707             # for the code that converts socket data to a HTTP::Request object
708 0         0 require HTTP::Daemon;
709              
710 0         0 my $conf_file_flag = 0;
711 0         0 my $port_flag = 0;
712 0         0 my $host_flag = 0;
713              
714             # Disable critic on the C-style for-loop because we need to step by
715             # 2 as we check for Net::Server arguments...
716 0         0 for (my $i = 0; $i < @_; $i += 2) ## no critic (ProhibitCStyleForLoops)
717             {
718 0 0       0 if ($_[$i] eq 'conf_file') { $conf_file_flag = 1; }
  0         0  
719 0 0       0 if ($_[$i] eq 'port') { $port_flag = 1; }
  0         0  
720 0 0       0 if ($_[$i] eq 'host') { $host_flag = 1; }
  0         0  
721             }
722              
723             # An explicitly-given conf-file trumps any specified at creation
724 0 0 0     0 if (exists($self->{conf_file}) and (!$conf_file_flag))
725             {
726 0         0 push @_, 'conf_file', $self->{conf_file};
727 0         0 $conf_file_flag = 1;
728             }
729              
730             # Don't do this next part if they've already given a port, or are
731             # pointing to a config file:
732 0 0 0     0 if (! ($conf_file_flag || $port_flag))
733             {
734 0   0     0 push @_, 'port', $self->{port} || $self->port || 9000;
735 0   0     0 push @_, 'host', $self->{host} || $self->host || q{*};
736             }
737              
738             # Try to load the Net::Server::MultiType module
739 0 0       0 if (! eval { require Net::Server::MultiType; 1; })
  0         0  
  0         0  
740             {
741 0 0       0 if ($@)
742             {
743 0         0 return ref($self) .
744             "::server_loop: Error loading Net::Server::MultiType: $@";
745             }
746             }
747 0         0 unshift @RPC::XML::Server::ISA, 'Net::Server::MultiType';
748              
749 0         0 $self->started('set');
750             # ...and we're off!
751 0         0 $self->run(@_);
752             }
753              
754 0         0 return;
755             }
756              
757             ###############################################################################
758             #
759             # Sub Name: post_configure_loop
760             #
761             # Description: Called by the Net::Server classes after all the config
762             # steps have been done and merged.
763             #
764             # Arguments: NAME IN/OUT TYPE DESCRIPTION
765             # $self in ref Class object
766             #
767             # Returns: $self
768             #
769             ###############################################################################
770             sub post_configure_hook
771             {
772 0     0 0 0 my $self = shift;
773              
774 0         0 $self->{__host} = $self->{server}->{host};
775 0         0 $self->{__port} = $self->{server}->{port};
776              
777 0         0 return $self;
778             }
779              
780             ###############################################################################
781             #
782             # Sub Name: pre_loop_hook
783             #
784             # Description: Called by Net::Server classes after the post_bind method,
785             # but before the socket-accept loop starts.
786             #
787             # Arguments: NAME IN/OUT TYPE DESCRIPTION
788             # $self in ref Object instance
789             #
790             # Returns: $self
791             #
792             ###############################################################################
793             sub pre_loop_hook
794             {
795 0     0 0 0 my $self = shift;
796              
797             # We have to disable the __DIE__ handler for the sake of XML::Parser::Expat
798 0         0 $SIG{__DIE__} = q{}; ## no critic (RequireLocalizedPunctuationVars)
799              
800 0         0 return $self;
801             }
802              
803             ###############################################################################
804             #
805             # Sub Name: process_request
806             #
807             # Description: This is provided for the case when we run as a subclass
808             # of Net::Server.
809             #
810             # Arguments: NAME IN/OUT TYPE DESCRIPTION
811             # $self in ref This class object
812             # $conn in ref If present, it's a connection
813             # object from HTTP::Daemon
814             #
815             # Returns: void
816             #
817             ###############################################################################
818             sub process_request ## no critic (ProhibitExcessComplexity)
819             {
820 1     1 0 12 my $self = shift;
821 1         2 my $conn = shift;
822              
823             my (
824 1         1 $req, $reqxml, $resp, $respxml, $do_compress,
825             $parser, $com_engine, $length, $read, $buf,
826             $resp_fh, $tmpdir, $peeraddr, $peerhost, $peerport
827             );
828              
829 1         10 my $me = ref($self) . '::process_request';
830 1 50       7 if (! $conn)
831             {
832             # Maintain compatibility with Net::Server 0.99, which does not pass
833             # the connection object at all:
834 0         0 $conn = $self->{server}->{client};
835             }
836 1 50       10 if (ref($conn) =~ /^Net::Server::Proto/)
837             {
838 0         0 bless $conn, 'HTTP::Daemon::ClientConn';
839 0         0 ${*{$conn}}{'httpd_daemon'} = $self;
  0         0  
  0         0  
840              
841 0 0 0     0 if ($IO::Socket::SSL::VERSION &&
842             $RPC::XML::Server::IO_SOCKET_SSL_HACK_NEEDED)
843             {
844 8     8   41 no strict 'vars'; ## no critic (ProhibitNoStrict)
  8         9  
  8         15659  
845             # RT 43019: Don't do this if Socket6/IO::Socket::INET6 is in
846             # effect, as it causes calls to unpack_sockaddr_in6 to break.
847 0 0 0     0 if (! (defined $Socket6::VERSION ||
848             defined $IO::Socket::INET6::VERSION))
849             {
850 0         0 unshift @HTTP::Daemon::ClientConn::ISA, 'IO::Socket::SSL';
851             }
852              
853 0         0 $RPC::XML::Server::IO_SOCKET_SSL_HACK_NEEDED = 0;
854             }
855             }
856              
857             # These will be attached to any and all request objects that are
858             # (successfully) read from $conn.
859 1         18 $peeraddr = $conn->peeraddr;
860 1         58 $peerport = $conn->peerport;
861 1         21 $peerhost = $conn->peerhost;
862 1   33     42 while ($conn and $req = $conn->get_request('headers only'))
863             {
864 1 50       767 if ($req->method eq 'HEAD')
    50          
865             {
866             # The HEAD method will be answered with our return headers,
867             # both as a means of self-identification and a verification
868             # of live-status. All the headers were pre-set in the cached
869             # HTTP::Response object. Also, we don't count this for stats.
870 0         0 $conn->send_response($self->response);
871             }
872             elsif ($req->method eq 'POST')
873             {
874             # Get a XML::Parser::ExpatNB object
875 1         34 $parser = $self->parser->parse();
876              
877 0         0 $do_compress = 0; # in case it was set for a previous response
878 0 0 0     0 if (($req->content_encoding || q{}) =~ $self->compress_re)
879             {
880 0 0       0 if (! $self->compress)
881             {
882 0         0 $conn->send_error(RC_BAD_REQUEST,
883             "$me: Compression not permitted in " . 'requests');
884 0         0 next;
885             }
886              
887 0         0 $do_compress = 1;
888             }
889              
890 0 0 0     0 if (($req->content_encoding || q{}) =~ /chunked/i)
891             {
892             # Technically speaking, we're not supposed to honor chunked
893             # transfer-encoding...
894 0         0 croak "$me: 'chunked' content-encoding not (yet) supported";
895             }
896             else
897             {
898 0         0 $length = $req->content_length;
899 0 0       0 if ($do_compress)
900             {
901             # Spin up the compression engine
902 0 0       0 if (! ($com_engine = Compress::Zlib::inflateInit()))
903             {
904 0         0 $conn->send_error(RC_INTERNAL_SERVER_ERROR,
905             "$me: Unable to initialize the " .
906             'Compress::Zlib engine');
907 0         0 next;
908             }
909             }
910              
911 0         0 $buf = q{};
912 0         0 while ($length > 0)
913             {
914 0 0       0 if ($buf = $conn->read_buffer)
915             {
916             # Anything that get_request read, but didn't use, was
917             # left in the read buffer. The call to sysread() should
918             # NOT be made until we've emptied this source, first.
919 0         0 $read = length $buf;
920 0         0 $conn->read_buffer(q{}); # Clear it, now that it's read
921             }
922             else
923             {
924 0 0       0 $read = sysread $conn, $buf,
925             ($length < 2048) ? $length : 2048;
926 0 0       0 if (! $read)
927             {
928             # Convert this print to a logging-hook call.
929             # Umm, when I have real logging hooks, I mean.
930             # The point is, odds are very good that $conn is
931             # dead to us now, and I don't want this package
932             # taking over SIGPIPE as well as the ones it
933             # already monopolizes.
934             #print STDERR "Error: Connection Dropped\n";
935 0         0 return;
936             }
937             }
938 0         0 $length -= $read;
939 0 0       0 if ($do_compress)
940             {
941 0 0       0 if (! ($buf = $com_engine->inflate($buf)))
942             {
943 0         0 $conn->send_error(RC_INTERNAL_SERVER_ERROR,
944             "$me: Error inflating " . 'compressed data');
945             # This error also means that even if Keep-Alive
946             # is set, we don't know how much of the stream
947             # is corrupted.
948 0         0 $conn->force_last_request;
949 0         0 next;
950             }
951             }
952              
953 0 0       0 if (! eval { $parser->parse_more($buf); 1; })
  0         0  
  0         0  
954             {
955 0 0       0 if ($@)
956             {
957 0         0 $conn->send_error(
958             RC_INTERNAL_SERVER_ERROR,
959             "$me: Parse error in (compressed) " .
960             "XML request (mid): $@"
961             );
962             # Again, the stream is likely corrupted
963 0         0 $conn->force_last_request;
964 0         0 next;
965             }
966             }
967             }
968              
969 0 0       0 if (! eval { $reqxml = $parser->parse_done(); 1; })
  0         0  
  0         0  
970             {
971 0 0       0 if ($@)
972             {
973 0         0 $conn->send_error(RC_INTERNAL_SERVER_ERROR,
974             "$me: Parse error in (compressed) " .
975             "XML request (end): $@");
976 0         0 next;
977             }
978             }
979             }
980              
981             # Dispatch will always return a RPC::XML::response.
982             # RT29351: If there was an error from RPC::XML::ParserFactory
983             # (such as a message that didn't conform to spec), then return it
984             # directly as a fault, don't have dispatch() try and handle it.
985 0 0       0 if (ref $reqxml)
986             {
987             # Set localized keys on $self, based on the connection info
988             ## no critic (ProhibitLocalVars)
989 0         0 local $self->{peeraddr} = $peeraddr;
990 0         0 local $self->{peerhost} = $peerhost;
991 0         0 local $self->{peerport} = $peerport;
992 0         0 local $self->{request} = $req;
993 0         0 $respxml = $self->dispatch($reqxml);
994             }
995             else
996             {
997 0         0 $respxml = RPC::XML::response->new(
998             $self->server_fault('badxml', $reqxml));
999             }
1000              
1001             # Clone the pre-fab response and set headers
1002 0         0 $resp = $self->response->clone;
1003             # Should we apply compression to the outgoing response?
1004 0         0 $do_compress = 0; # In case it was set above for incoming data
1005 0 0 0     0 if ($self->compress &&
      0        
      0        
1006             ($respxml->length > $self->compress_thresh) &&
1007             (($req->header('Accept-Encoding') || q{}) =~
1008             $self->compress_re))
1009             {
1010 0         0 $do_compress = 1;
1011 0         0 $resp->header(Content_Encoding => $self->compress);
1012             }
1013             # Next step, determine the response disposition. If it is above the
1014             # threshhold for a requested file cut-off, send it to a temp file
1015 0 0 0     0 if ($self->message_file_thresh &&
1016             $self->message_file_thresh < $respxml->length)
1017             {
1018             # Start by creating a temp-file
1019 0   0     0 $tmpdir = $self->message_temp_dir || File::Spec->tmpdir;
1020             # File::Temp->new() croaks on error
1021             $resp_fh =
1022 0         0 eval { File::Temp->new(UNLINK => 1, DIR => $tmpdir) };
  0         0  
1023 0 0       0 if (! $resp_fh)
1024             {
1025 0         0 $conn->send_error(
1026             RC_INTERNAL_SERVER_ERROR,
1027             "$me: Error opening tmpfile: $@"
1028             );
1029 0         0 next;
1030             }
1031             # Make it auto-flush
1032 0         0 $resp_fh->autoflush();
1033              
1034             # Now that we have it, spool the response to it. This is a
1035             # little hairy, since we still have to allow for compression.
1036             # And though the response could theoretically be HUGE, in
1037             # order to compress we have to write it to a second temp-file
1038             # first, so that we can compress it into the primary handle.
1039 0 0       0 if ($do_compress)
1040             {
1041             my $fh_compress =
1042 0         0 eval { File::Temp->new(UNLINK => 1, DIR => $tmpdir) };
  0         0  
1043 0 0       0 if (! $fh_compress)
1044             {
1045 0         0 $conn->send_error(
1046             RC_INTERNAL_SERVER_ERROR,
1047             "$me: Error opening compression tmpfile: $@"
1048             );
1049 0         0 next;
1050             }
1051             # Make it auto-flush
1052 0         0 $fh_compress->autoflush();
1053              
1054             # Write the request to the second FH
1055 0         0 $respxml->serialize($fh_compress);
1056 0         0 seek $fh_compress, 0, 0;
1057              
1058             # Spin up the compression engine
1059 0 0       0 if (! ($com_engine = Compress::Zlib::deflateInit()))
1060             {
1061 0         0 $conn->send_error(RC_INTERNAL_SERVER_ERROR,
1062             "$me: Unable to initialize the " .
1063             'Compress::Zlib engine');
1064 0         0 next;
1065             }
1066              
1067             # Spool from the second FH through the compression engine,
1068             # into the intended FH.
1069 0         0 $buf = q{};
1070 0         0 my $out;
1071 0         0 while (read $fh_compress, $buf, 4096)
1072             {
1073 0 0       0 if (! defined($out = $com_engine->deflate(\$buf)))
1074             {
1075 0         0 $conn->send_error(RC_INTERNAL_SERVER_ERROR,
1076             "$me: Compression failure in " . 'deflate()');
1077 0         0 next;
1078             }
1079 0         0 print {$resp_fh} $out;
  0         0  
1080             }
1081             # Make sure we have all that's left
1082 0 0       0 if (! defined($out = $com_engine->flush))
1083             {
1084 0         0 $conn->send_error(RC_INTERNAL_SERVER_ERROR,
1085             "$me: Compression flush failure in deflate()");
1086 0         0 next;
1087             }
1088 0         0 print {$resp_fh} $out;
  0         0  
1089              
1090             # Close the secondary FH. Rewinding the primary is done
1091             # later.
1092 0 0       0 if (! close $fh_compress)
1093             {
1094 0         0 carp "Error closing temp file: $!";
1095             }
1096             }
1097             else
1098             {
1099 0         0 $respxml->serialize($resp_fh);
1100             }
1101 0         0 seek $resp_fh, 0, 0;
1102              
1103 0         0 $resp->content_length(-s $resp_fh);
1104             $resp->content(
1105             sub {
1106 0     0   0 my $buffer = q{};
1107 0 0       0 if (! defined(read $resp_fh, $buffer, 4096))
1108             {
1109 0         0 return;
1110             }
1111 0         0 $buffer;
1112             }
1113 0         0 );
1114             }
1115             else
1116             {
1117             # Treat the content strictly in-memory
1118 0         0 utf8::encode($buf = $respxml->as_string);
1119 0 0       0 if ($do_compress)
1120             {
1121 0         0 $buf = Compress::Zlib::compress($buf);
1122             }
1123 0         0 $resp->content($buf);
1124             # With $buf force-downgraded to octets, length() should work
1125 0         0 $resp->content_length(length $buf);
1126             }
1127              
1128 0         0 my $eval = eval {
1129 0     0   0 local $SIG{PIPE} = sub { die "Caught SIGPIPE\n"; };
  0         0  
1130 0         0 $conn->send_response($resp);
1131 0         0 1;
1132             };
1133 0 0 0     0 if (! $eval && $@ && $@ =~ /Caught SIGPIPE/)
      0        
1134             {
1135             # Client disconnected, maybe even before we started sending
1136             # our response. Either way, $conn is useless now.
1137 0         0 undef $conn;
1138             }
1139 0         0 undef $resp;
1140             }
1141             else
1142             {
1143 0         0 $conn->send_error(RC_FORBIDDEN);
1144             }
1145             }
1146              
1147 0         0 return;
1148             }
1149              
1150             ###############################################################################
1151             #
1152             # Sub Name: dispatch
1153             #
1154             # Description: Route the request by parsing it, determining what the
1155             # Perl routine should be, etc.
1156             #
1157             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1158             # $self in ref Object of this class
1159             # $xml in ref Reference to the XML text, or
1160             # a RPC::XML::request object.
1161             # If it is a listref, assume
1162             # [ name, @args ].
1163             # $reftable in hashref If present, a reference to the
1164             # current-running table of
1165             # back-references
1166             #
1167             # Returns: RPC::XML::response object
1168             #
1169             ###############################################################################
1170             sub dispatch
1171             {
1172 0     0 1 0 my ($self, $xml) = @_;
1173              
1174 0         0 my ($reqobj, @args, $response, $name, $meth);
1175              
1176 0 0 0     0 if (ref $xml eq 'SCALAR')
    0          
    0          
1177             {
1178 0         0 $reqobj = $self->parser->parse(${$xml});
  0         0  
1179 0 0       0 if (! ref $reqobj)
1180             {
1181 0         0 return RPC::XML::response->
1182             new($self->server_fault(badxml => $reqobj));
1183             }
1184             }
1185             elsif (ref $xml eq 'ARRAY')
1186             {
1187             # This is sort of a cheat, to make the system.multicall API call a
1188             # lot easier. The syntax isn't documented in the manual page, for good
1189             # reason.
1190 0         0 $reqobj = RPC::XML::request->new(@{$xml});
  0         0  
1191             }
1192             elsif (blessed $xml && $xml->isa('RPC::XML::request'))
1193             {
1194 0         0 $reqobj = $xml;
1195             }
1196             else
1197             {
1198 0         0 $reqobj = $self->parser->parse($xml);
1199 0 0       0 if (! ref $reqobj)
1200             {
1201 0         0 return RPC::XML::response->
1202             new($self->server_fault(badxml => $reqobj));
1203             }
1204             }
1205              
1206 0         0 @args = @{$reqobj->args};
  0         0  
1207 0         0 $name = $reqobj->name;
1208              
1209             # Get the method, call it, and bump the internal requests counter. Create
1210             # a fault object if there is problem with the method object itself.
1211 0         0 $meth = $self->get_method($name);
1212 0 0       0 if (ref $meth)
1213             {
1214 0         0 $response = $meth->call($self, @args);
1215 0 0 0     0 if (! (($name eq 'system.status') &&
      0        
      0        
1216             @args &&
1217             ($args[0]->type eq 'boolean') &&
1218             ($args[0]->value)))
1219             {
1220 0         0 $self->{__requests}++;
1221             }
1222             }
1223             else
1224             {
1225 0         0 $response = $self->server_fault(
1226             badmethod => "No method '$meth' on server"
1227             );
1228             }
1229              
1230             # All the eval'ing and error-trapping happened within the method class
1231 0         0 return RPC::XML::response->new($response);
1232             }
1233              
1234             ###############################################################################
1235             #
1236             # Sub Name: call
1237             #
1238             # Description: This is an internal, end-run-around-dispatch() method to
1239             # allow the RPC methods that this server has and knows about
1240             # to call each other through their reference to the server
1241             # object.
1242             #
1243             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1244             # $self in ref Object of this class
1245             # $name in scalar Name of the method to call
1246             # @args in list Arguments (if any) to pass
1247             #
1248             # Returns: Success: return value of the call
1249             # Failure: error string
1250             #
1251             ###############################################################################
1252             sub call
1253             {
1254 0     0 0 0 my ($self, $name, @args) = @_;
1255              
1256 0         0 my $meth;
1257              
1258             # Two VERY important notes here: The values in @args are not pre-treated
1259             # in any way, so not only should the receiver understand what they're
1260             # getting, there's no signature checking taking place, either.
1261             #
1262             # Second, if the normal return value is not distinguishable from a string,
1263             # then the caller may not recognize if an error occurs.
1264              
1265 0         0 $meth = $self->get_method($name);
1266 0 0       0 if (! ref $meth)
1267             {
1268 0         0 return $meth;
1269             }
1270              
1271 0         0 return $meth->call($self, @args);
1272             }
1273              
1274             ###############################################################################
1275             #
1276             # Sub Name: add_default_methods
1277             #
1278             # Description: This adds all the methods that were shipped with this
1279             # package, by threading through to add_methods_in_dir()
1280             # with the global constant $INSTALL_DIR.
1281             #
1282             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1283             # $self in ref Object reference/static class
1284             # @details in ref Details of names to add or skip
1285             #
1286             # Returns: $self
1287             #
1288             ###############################################################################
1289             sub add_default_methods
1290             {
1291 1     1 1 2 my ($self, @details) = @_;
1292              
1293 1         6 return $self->add_methods_in_dir($self->INSTALL_DIR, @details);
1294             }
1295              
1296             ###############################################################################
1297             #
1298             # Sub Name: add_methods_in_dir
1299             #
1300             # Description: This adds all methods specified in the directory passed,
1301             # in accordance with the details specified.
1302             #
1303             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1304             # $self in ref Class instance
1305             # $dir in scalar Directory to scan
1306             # @details in list Possible hanky-panky with the
1307             # list of methods to install
1308             #
1309             # Returns: $self
1310             #
1311             ###############################################################################
1312             sub add_methods_in_dir
1313             {
1314 1     1 1 2 my ($self, $dir, @details) = @_;
1315              
1316 1         2 my $negate = 0;
1317 1         2 my $detail = 0;
1318 1         1 my (%details, $ret);
1319              
1320 1 50       6 if (@details)
1321             {
1322 0         0 $detail = 1;
1323 0 0       0 if ($details[0] =~ /^-?except/i)
1324             {
1325 0         0 $negate = 1;
1326 0         0 shift @details;
1327             }
1328 0         0 for (@details)
1329             {
1330 0 0       0 if (! /[.]xpl$/)
1331             {
1332 0         0 $_ .= '.xpl';
1333             }
1334             }
1335 0         0 @details{@details} = (1) x @details;
1336             }
1337              
1338 1         1 my $dh;
1339 1 50       37 if (! opendir $dh, $dir)
1340             {
1341 0         0 return "Error opening $dir for reading: $!";
1342             }
1343 1         52 my @files = grep { $_ =~ /[.]xpl$/ } readdir $dh;
  15         22  
1344 1         10 closedir $dh;
1345              
1346 1         2 for my $file (@files)
1347             {
1348             # Use $detail as a short-circuit to avoid the other tests when we can
1349 1 0 33     3 if ($detail &&
    0          
1350             ($negate ? $details{$file} : ! $details{$file}))
1351             {
1352 0         0 next;
1353             }
1354             # n.b.: Giving the full path keeps add_method from having to search
1355 1         20 $ret = $self->add_method(File::Spec->catfile($dir, $file));
1356 0 0       0 if (! ref $ret)
1357             {
1358 0         0 return $ret;
1359             }
1360             }
1361              
1362 0         0 return $self;
1363             }
1364              
1365             # For name-symmetry:
1366             *add_procedures_in_dir = *add_functions_in_dir = \&add_methods_in_dir;
1367              
1368             ###############################################################################
1369             #
1370             # Sub Name: delete_method
1371             #
1372             # Description: Remove any current binding for the named method on the
1373             # calling server object. Note that if this method is shared
1374             # across other server objects, it won't be destroyed until
1375             # the last server deletes it.
1376             #
1377             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1378             # $self in ref Object of this class
1379             # $name in scalar Name of method to lost
1380             #
1381             # Returns: Success: $self
1382             # Failure: error message
1383             #
1384             ###############################################################################
1385             sub delete_method
1386             {
1387 0     0 1 0 my ($self, $name) = @_;
1388              
1389 0 0       0 if ($name)
1390             {
1391 0 0       0 if ($self->{__method_table}->{$name})
1392             {
1393 0         0 delete $self->{__method_table}->{$name};
1394             }
1395             }
1396             else
1397             {
1398 0         0 return ref $self . "::delete_method: No such method $name";
1399             }
1400              
1401 0         0 return $self;
1402             }
1403              
1404             # For name-symmetry:
1405             *delete_procedure = *delete_function = \&delete_method;
1406              
1407             ###############################################################################
1408             #
1409             # Sub Name: list_methods
1410             #
1411             # Description: Return a list of the methods this object has published.
1412             # Returns the names, not the objects.
1413             #
1414             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1415             # $self in ref Object of this class
1416             #
1417             # Returns: List of names, possibly empty
1418             #
1419             ###############################################################################
1420             sub list_methods
1421             {
1422 0     0 1 0 return keys %{shift->{__method_table}};
  0         0  
1423             }
1424              
1425             # For name-symmetry:
1426             *list_procedures = *list_functions = \&list_methods;
1427              
1428             ###############################################################################
1429             #
1430             # Sub Name: share_methods
1431             #
1432             # Description: Share the named methods as found on $src_srv into the
1433             # method table of the calling object.
1434             #
1435             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1436             # $self in ref Object of this class
1437             # $src_srv in ref Another object of this class
1438             # @names in list One or more method names
1439             #
1440             # Returns: Success: $self
1441             # Failure: error message
1442             #
1443             ###############################################################################
1444             sub share_methods
1445             {
1446 0     0 1 0 my ($self, $src_srv, @names) = @_;
1447              
1448 0         0 my ($me, $pkg, %methods, @methods, $meth, @list, @missing);
1449              
1450 0         0 $me = ref($self) . '::share_methods';
1451 0         0 $pkg = __PACKAGE__; # So it can go inside quoted strings
1452              
1453 0 0 0     0 if (! (blessed $src_srv && $src_srv->isa($pkg)))
1454             {
1455 0         0 return "$me: First arg not derived from $pkg, cannot share";
1456             }
1457 0 0       0 if (! @names)
1458             {
1459 0         0 return "$me: Must specify at least one method name for sharing";
1460             }
1461              
1462             # Scan @names for any regex objects, and if found insert the matches into
1463             # the list.
1464             #
1465             # Only do this once:
1466 0         0 @methods = keys %{$src_srv->{__method_table}};
  0         0  
1467 0         0 for my $name (@names)
1468             {
1469 0 0       0 if (ref $name eq 'Regexp')
1470             {
1471 0         0 for (grep { $_ =~ $name } @methods)
  0         0  
1472             {
1473 0         0 $methods{$_}++;
1474             }
1475             }
1476             else
1477             {
1478 0         0 $methods{$name}++;
1479             }
1480             }
1481             # This has the benefit of trimming any redundancies caused by regex's
1482 0         0 @names = keys %methods;
1483              
1484             # Note that the method refs are saved until we've verified all of them.
1485             # If we have to return a failure message, I don't want to leave a half-
1486             # finished job or have to go back and undo (n-1) additions because of one
1487             # failure.
1488 0         0 for (@names)
1489             {
1490 0         0 $meth = $src_srv->get_method($_);
1491 0 0       0 if (ref $meth)
1492             {
1493 0         0 push @list, $meth;
1494             }
1495             else
1496             {
1497 0         0 push @missing, $_;
1498             }
1499             }
1500              
1501 0 0       0 if (@missing)
1502             {
1503 0         0 return "$me: One or more methods not found on source object: " .
1504             join q{ } => @missing;
1505             }
1506             else
1507             {
1508 0         0 for (@list)
1509             {
1510 0         0 $self->add_method($_);
1511             }
1512             }
1513              
1514 0         0 return $self;
1515             }
1516              
1517             # For name-symmetry:
1518             *share_procedures = *share_functions = \&share_methods;
1519              
1520             ###############################################################################
1521             #
1522             # Sub Name: copy_methods
1523             #
1524             # Description: Copy the named methods as found on $src_srv into the
1525             # method table of the calling object. This differs from
1526             # share() above in that only the coderef is shared, the
1527             # rest of the method is a completely new object.
1528             #
1529             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1530             # $self in ref Object of this class
1531             # $src_srv in ref Another object of this class
1532             # @names in list One or more method names
1533             #
1534             # Returns: Success: $self
1535             # Failure: error message
1536             #
1537             ###############################################################################
1538             sub copy_methods
1539             {
1540 0     0 1 0 my ($self, $src_srv, @names) = @_;
1541              
1542 0         0 my ($me, $pkg, %methods, @methods, $meth, @list, @missing);
1543              
1544 0         0 $me = ref($self) . '::copy_methods';
1545 0         0 $pkg = __PACKAGE__; # So it can go inside quoted strings
1546              
1547 0 0 0     0 if (! (blessed $src_srv && $src_srv->isa($pkg)))
1548             {
1549 0         0 return "$me: First arg not derived from $pkg, cannot copy";
1550             }
1551 0 0       0 if (! @names)
1552             {
1553 0         0 return "$me: Must specify at least one method name/regex for copying";
1554             }
1555              
1556             # Scan @names for any regez objects, and if found insert the matches into
1557             # the list.
1558             #
1559             # Only do this once:
1560 0         0 @methods = keys %{$src_srv->{__method_table}};
  0         0  
1561 0         0 for my $name (@names)
1562             {
1563 0 0       0 if (ref $name eq 'Regexp')
1564             {
1565 0         0 for (grep { $_ =~ $name } @methods)
  0         0  
1566             {
1567 0         0 $methods{$_}++;
1568             }
1569             }
1570             else
1571             {
1572 0         0 $methods{$name}++;
1573             }
1574             }
1575             # This has the benefit of trimming any redundancies caused by regex's
1576 0         0 @names = keys %methods;
1577              
1578             # Note that the method clones are saved until we've verified all of them.
1579             # If we have to return a failure message, I don't want to leave a half-
1580             # finished job or have to go back and undo (n-1) additions because of one
1581             # failure.
1582 0         0 for (@names)
1583             {
1584 0         0 $meth = $src_srv->get_method($_);
1585 0 0       0 if (ref $meth)
1586             {
1587 0         0 push @list, $meth->clone;
1588             }
1589             else
1590             {
1591 0         0 push @missing, $_;
1592             }
1593             }
1594              
1595 0 0       0 if (@missing)
1596             {
1597 0         0 return "$me: One or more methods not found on source object: @missing";
1598             }
1599             else
1600             {
1601 0         0 for (@list)
1602             {
1603 0         0 $self->add_method($_);
1604             }
1605             }
1606              
1607 0         0 return $self;
1608             }
1609              
1610             # For name-symmetry:
1611             *copy_procedures = *copy_functions = \©_methods;
1612              
1613             ###############################################################################
1614             #
1615             # Sub Name: timeout
1616             #
1617             # Description: This sets the timeout for processing connections after
1618             # a new connection has been accepted. It returns the old
1619             # timeout value. If you pass in no value, it returns
1620             # the current timeout.
1621             #
1622             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1623             # $self in ref Object reference/static class
1624             # $timeout in ref New timeout value
1625             #
1626             # Returns: $self->{__timeout}
1627             #
1628             ###############################################################################
1629             sub timeout
1630             {
1631 1     1 1 1 my ($self, $timeout) = @_;
1632              
1633 1         3 my $old_timeout = $self->{__timeout};
1634 1 50       6 if ($timeout)
1635             {
1636 0         0 $self->{__timeout} = $timeout;
1637             }
1638              
1639 1         12 return $old_timeout;
1640             }
1641              
1642             ###############################################################################
1643             #
1644             # Sub Name: server_fault
1645             #
1646             # Description: Create a RPC::XML::fault object for the class of error
1647             # and specific message that are passed in.
1648             #
1649             # Arguments: NAME IN/OUT TYPE DESCRIPTION
1650             # $self in ref Object of this class
1651             # $err in scalar Type of error/fault to generate
1652             # $message in scalar Error text for the fault
1653             #
1654             # Returns: RPC::XML::fault instance
1655             #
1656             ###############################################################################
1657             sub server_fault
1658             {
1659 0     0 1   my ($self, $err, $message) = @_;
1660 0   0       $message ||= q{}; # Avoid any "undef" warnings
1661              
1662 0           my ($code, $text);
1663              
1664 0 0         if (my $fault = $self->{__fault_table}->{$err})
1665             {
1666 0 0         if (ref $fault)
1667             {
1668             # This specifies both code and message
1669 0           ($code, $text) = @{$fault};
  0            
1670             # Replace (the first) "%s" with $message
1671 0           $text =~ s/%s/$message/;
1672             }
1673             else
1674             {
1675             # This is just the code, use $message verbatim
1676 0           ($code, $text) = ($fault, $message);
1677             }
1678             }
1679             else
1680             {
1681 0           $code = -1;
1682 0           $text = "Unknown error class '$err' (message is '$message')";
1683             }
1684              
1685 0           return RPC::XML::fault->new($code, $text);
1686             }
1687              
1688             1;
1689              
1690             __END__