############################################################################### # # This file copyright (c) 2001 by Randy J. Ray , # all rights reserved # # Copying and distribution are permitted under the terms of the Artistic # License as distributed with Perl versions 5.002 and later. See # http://www.opensource.org/licenses/artistic-license.php # ############################################################################### # # $Id: Server.pm,v 1.44 2006/06/04 07:44:41 rjray Exp $ # # Description: This class implements an RPC::XML server, using the core # XML::RPC transaction code. The server may be created with # or without an HTTP::Daemon object instance to answer the # requests. # # Functions: new # version # url # product_tokens # started # path # host # port # requests # response # compress # compress_thresh # compress_re # message_file_thresh # message_temp_dir # xpl_path # add_method # method_from_file # get_method # server_loop # post_configure_hook # pre_loop_hook # process_request # dispatch # call # add_default_methods # add_methods_in_dir # delete_method # list_methods # share_methods # copy_methods # timeout # # Libraries: AutoLoader # HTTP::Daemon # HTTP::Response # HTTP::Status # URI # RPC::XML # RPC::XML::Parser # RPC::XML::Procedure # # Global Consts: $VERSION # $INSTALL_DIR # ############################################################################### package RPC::XML::Server; use 5.005; use strict; use vars qw($VERSION @ISA $INSTANCE $INSTALL_DIR @XPL_PATH); use Carp 'carp'; use AutoLoader 'AUTOLOAD'; use File::Spec; BEGIN { $INSTALL_DIR = (File::Spec->splitpath(__FILE__))[1]; @XPL_PATH = ($INSTALL_DIR, File::Spec->curdir); } use HTTP::Status; require HTTP::Response; require URI; use RPC::XML 'bytelength'; require RPC::XML::Parser; require RPC::XML::Procedure; $VERSION = do { my @r=(q$Revision: 1.44 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r }; ############################################################################### # # Sub Name: new # # Description: Create a new RPC::XML::Server object. This entails getting # a HTTP::Daemon object, saving several internal values, and # other operations. # # Arguments: NAME IN/OUT TYPE DESCRIPTION # $class in scalar Ref or string for the class # %args in hash Additional arguments # # Returns: Success: object reference # Failure: error string # ############################################################################### sub new { my $class = shift; my %args = @_; my ($self, $http, $resp, $host, $port, $queue, $path, $URI, $srv_name, $srv_version, $timeout); $class = ref($class) || $class; $self = bless {}, $class; $srv_version = $args{server_version} || $self->version; $srv_name = $args{server_name} || $class; $self->{__version} = "$srv_name/$srv_version"; if ($args{no_http}) { $self->{__host} = $args{host} || ''; $self->{__port} = $args{port} || ''; delete @args{qw(host port)}; } else { require HTTP::Daemon; $host = $args{host} || ''; $port = $args{port} || ''; $queue = $args{queue} || 5; $http = HTTP::Daemon->new(Reuse => 1, ($host ? (LocalHost => $host) : ()), ($port ? (LocalPort => $port) : ()), ($queue ? (Listen => $queue) : ())); return "${class}::new: Unable to create HTTP::Daemon object" unless $http; $URI = URI->new($http->url); $self->{__host} = $URI->host; $self->{__port} = $URI->port; $self->{__daemon} = $http; # Remove those we've processed delete @args{qw(host port queue)}; } $resp = HTTP::Response->new(); return "${class}::new: Unable to create HTTP::Response object" unless $resp; $resp->header(# This is essentially the same string returned by the # default "identity" method that may be loaded from a # XPL file. But it hasn't been loaded yet, and may not # be, hence we set it here (possibly from option values) RPC_Server => $self->{__version}, RPC_Encoding => 'XML-RPC', # Set any other headers as well Accept => 'text/xml'); $resp->content_type('text/xml'); $resp->code(RC_OK); $resp->message('OK'); $self->{__response} = $resp; $self->{__path} = $args{path} || ''; $self->{__started} = 0; $self->{__method_table} = {}; $self->{__requests} = 0; $self->{__auto_methods} = $args{auto_methods} || 0; $self->{__auto_updates} = $args{auto_updates} || 0; $self->{__debug} = $args{debug} || 0; $self->{__parser} = RPC::XML::Parser->new($args{parser} ? @{$args{parser}} : ()); $self->{__xpl_path} = $args{xpl_path} || []; $self->{__timeout} = $args{timeout} || 10; $self->add_default_methods unless ($args{no_default}); $self->{__compress} = ''; unless ($args{no_compress}) { eval "require Compress::Zlib"; $self->{__compress} = $@ ? '' : 'deflate'; # Add some more headers to the default response object for compression. # It looks wasteful to keep using the hash key, but it makes it easier # to change the string in just one place (above) if I have to. $resp->header(Accept_Encoding => $self->{__compress}) if $self->{__compress}; $self->{__compress_thresh} = $args{compress_thresh} || 4096; # Yes, I know this is redundant. It's for future expansion/flexibility. $self->{__compress_re} = $self->{__compress} ? qr/$self->{__compress}/ : qr/deflate/; } # Parameters to control the point at which messages are shunted to temp # files due to size, and where to home the temp files. Start with a size # threshhold of 1Meg and no specific dir (which will fall-through to the # tmpdir() method of File::Spec). $self->{__message_file_thresh} = $args{message_file_thresh} || 1048576; $self->{__message_temp_dir} = $args{message_temp_dir} || ''; # Remove the args we've already dealt with directly delete @args{qw(no_default no_http debug path server_name server_version no_compress compress_thresh parser message_file_thresh message_temp_dir)}; # Copy the rest over untouched $self->{$_} = $args{$_} for (keys %args); $self; } # Most of these tiny subs are accessors to the internal hash keys. They not # only control access to the internals, they ease sub-classing. sub version { $RPC::XML::Server::VERSION } sub INSTALL_DIR { $INSTALL_DIR } sub url { my $self = shift; return $self->{__daemon}->url if $self->{__daemon}; return undef unless (my $host = $self->host); my $path = $self->path; my $port = $self->port; if ($port == 443) { return "https://$host$path"; } elsif ($port == 80) { return "http://$host$path"; } else { return "http://$host:$port$path"; } } sub product_tokens { sprintf "%s/%s", (ref $_[0] || $_[0]), $_[0]->version; } # This fetches/sets the internal "started" timestamp sub started { my $self = shift; my $set = shift || 0; my $old = $self->{__started} || 0; $self->{__started} = time if $set; $old; } # Fetch/set the compression threshhold sub compress_thresh { my $self = shift; my $set = shift || 0; my $old = $self->{__compress_thresh}; $self->{__compress_thresh} = $set if ($set); $old; } # Fetch/set the threshhold for spooling messages to files sub message_file_thresh { my $self = shift; my $set = shift || 0; my $old = $self->{__message_file_thresh}; $self->{__message_file_thresh} = $set if ($set); $old; } # Fetch/set the temp dir to use for spooling large messages to files sub message_temp_dir { my $self = shift; my $set = shift || 0; my $old = $self->{__message_temp_dir}; $self->{__message_temp_dir} = $set if ($set); $old; } BEGIN { no strict 'refs'; # These are immutable member values, so this simple block applies to all for my $method (qw(path host port requests response compress compress_re parser)) { *$method = sub { shift->{"__$method"} } } } # Get/set the search path for XPL files sub xpl_path { my $self = shift; my $ret = $self->{__xpl_path}; $self->{__xpl_path} = $_[0] if ($_[0] and ref($_[0]) eq 'ARRAY'); $ret; } ############################################################################### # # Sub Name: add_method # # Description: Add a funtion-to-method mapping to the server object. # # Arguments: NAME IN/OUT TYPE DESCRIPTION # $self in ref Object to add to # $meth in scalar Hash ref of data or file name # # Returns: Success: $self # Failure: error string # ############################################################################### sub add_method { my $self = shift; my $meth = shift; my ($name, $val); my $me = ref($self) . '::add_method'; if (! ref($meth)) { $val = $self->method_from_file($meth); if (! ref($val)) { return "$me: Error loading from file $meth: $val"; } else { $meth = $val; } } elsif (ref($meth) eq 'HASH') { my $class = 'RPC::XML::' . ucfirst ($meth->{type} || 'method'); $meth = $class->new($meth); } elsif (! UNIVERSAL::isa($meth, 'RPC::XML::Procedure')) { return "$me: Method argument must be a file name, a hash " . 'reference or an object derived from RPC::XML::Procedure'; } # Do some sanity-checks return "$me: Method missing required data; check name, code and/or " . 'signature' unless $meth->is_valid; $name = $meth->name; $self->{__method_table}->{$name} = $meth; $self; } 1; =pod =head1 NAME RPC::XML::Server - A sample server implementation based on RPC::XML =head1 SYNOPSIS use RPC::XML::Server; ... $srv = RPC::XML::Server->new(port => 9000); # Several of these, most likely: $srv->add_method(...); ... $srv->server_loop; # Never returns =head1 DESCRIPTION This is a sample XML-RPC server built upon the B data classes, and using B and B for the communication layer. =head1 USAGE Use of the B is based on an object model. A server is instantiated from the class, methods (subroutines) are made public by adding them through the object interface, and then the server object is responsible for dispatching requests (and possibly for the HTTP listening, as well). =head2 Static Methods These methods are static to the package, and are used to provide external access to internal settings: =over 4 =item INSTALL_DIR Returns the directory that this module is installed into. This is used by methods such as C to locate the XPL files that are shipped with the distribution. =item version Returns the version string associated with this package. =item product_tokens This returns the identifying string for the server, in the format C consistent with other applications such as Apache and B. It is provided here as part of the compatibility with B that is required for effective integration with B. =back =head2 Methods The following are object (non-static) methods. Unless otherwise explicitly noted, all methods return the invoking object reference upon success, and a non-reference error string upon failure. See L below for details of how the server class manages gzip-based compression and expansion of messages. =over 4 =item new(OPTIONS) Creates a new object of the class and returns the blessed reference. Depending on the options, the object will contain some combination of an HTTP listener, a pre-populated B object, a B object, and a dispatch table with the set of default methods pre-loaded. The options that B accepts are passed as a hash of key/value pairs (not a hash reference). The accepted options are: =over 4 =item B If passed with a C value, prevents the creation and storage of the B object. This allows for deployment of a server object in other environments. Note that if this is set, the B method described below will silently attempt to use the B module. =item B If passed with a C value, prevents the loading of the default methods provided with the B distribution. These may be later loaded using the B interface described later. The methods themselves are described below (see L<"The Default Methods Provided">). =item B =item B =item B =item B These four are specific to the HTTP-based nature of the server. The B argument sets the additional URI path information that clients would use to contact the server. Internally, it is not used except in outgoing status and introspection reports. The B, B and B arguments are passed to the B constructor if they are passed. They set the hostname, TCP/IP port, and socket listening queue, respectively. They may also be used if the server object tries to use B as an alternative server core. =item B If you plan to add methods to the server object by passing filenames to the C call, this argument may be used to specify one or more additional directories to be searched when the passed-in filename is a relative path. The value for this must be an array reference. See also B and B, below. =item B Specify a value (in seconds) for the B server to use as a timeout value when reading request data from an inbound connection. The default value is 10 seconds. This value is not used except by B. =item B If specified and set to a true value, enables the automatic searching for a requested remote method that is unknown to the server object handling the request. If set to "no" (or not set at all), then a request for an unknown function causes the object instance to report an error. If the routine is still not found, the error is reported. Enabling this is a security risk, and should only be permitted by a server administrator with fully informed acknowledgement and consent. =item B If specified and set to a "true" value, enables the checking of the modification time of the file from which a method was originally loaded. If the file has changed, the method is re-loaded before execution is handed off. As with the auto-loading of methods, this represents a security risk, and should only be permitted by a server administrator with fully informed acknowledgement and consent. =item B If this parameter is passed, the value following it is expected to be an array reference. The contents of that array are passed to the B method of the B object that the server object caches for its use. See the B manual page for a list of recognized parameters to the constructor. =item B If this key is passed, the value associated with it is assumed to be a numerical limit to the size of in-memory messages. Any out-bound request that would be larger than this when stringified is instead written to an anonynous temporary file, and spooled from there instead. This is useful for cases in which the request includes B objects that are themselves spooled from file-handles. This test is independent of compression, so even if compression of a request would drop it below this threshhold, it will be spooled anyway. The file itself is unlinked after the file-handle is created, so once it is freed the disk space is immediately freed. =item B If a message is to be spooled to a temporary file, this key can define a specific directory in which to open those files. If this is not given, then the C method from the B package is used, instead. =back Any other keys in the options hash not explicitly used by the constructor are copied over verbatim onto the object, for the benefit of sub-classing this class. All internal keys are prefixed with C<__> to avoid confusion. Feel free to use this prefix only if you wish to re-introduce confusion. =item url This returns the HTTP URL that the server will be responding to, when it is in the connection-accept loop. If the server object was created without a built-in HTTP listener, then this method returns C. =item requests Returns the number of requests this server object has marshalled. Note that in multi-process environments (such as Apache or Net::Server::PreFork) the value returned will only reflect the messages dispatched by the specific process itself. =item response Each instance of this class (and any subclasses that do not completely override the C method) creates and stores an instance of B, which is then used by the B or B processing loops in constructing the response to clients. The response object has all common headers pre-set for efficiency. This method returns a reference to that object. =item started([BOOL]) Gets and possibly sets the clock-time when the server starts accepting connections. If a value is passed that evaluates to true, then the current clock time is marked as the starting time. In either case, the current value is returned. The clock-time is based on the internal B