# ======================================================================
#
# Copyright (C) 2000-2001 Paul Kulchenko (paulclinger@yahoo.com)
# SOAP::Lite is free software; you can redistribute it
# and/or modify it under the same terms as Perl itself.
#
# $Id$
#
# ======================================================================
package XMLRPC::Lite;
use SOAP::Lite;
use strict;
our $VERSION = 0.717;
# ======================================================================
package XMLRPC::Constants;
BEGIN {
no strict 'refs';
for (qw(
FAULT_CLIENT FAULT_SERVER
HTTP_ON_SUCCESS_CODE HTTP_ON_FAULT_CODE
DO_NOT_USE_XML_PARSER DO_NOT_USE_CHARSET
DO_NOT_USE_LWP_LENGTH_HACK DO_NOT_CHECK_CONTENT_TYPE
)) {
*$_ = \${'SOAP::Constants::' . $_}
}
# XML-RPC spec requires content-type to be "text/xml"
$XMLRPC::Constants::DO_NOT_USE_CHARSET = 1;
}
# ======================================================================
package XMLRPC::Data;
@XMLRPC::Data::ISA = qw(SOAP::Data);
# ======================================================================
package XMLRPC::Serializer;
@XMLRPC::Serializer::ISA = qw(SOAP::Serializer);
sub new {
my $class = shift;
return $class if ref $class;
return $class->SUPER::new(
typelookup => {
base64 => [10, sub {$_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/}, 'as_base64'],
int => [20, sub {$_[0] =~ /^[+-]?\d+$/}, 'as_int'],
double => [30, sub {$_[0] =~ /^(-?(?:\d+(?:\.\d*)?|\.\d+)|([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)$/}, 'as_double'],
dateTime => [35, sub {$_[0] =~ /^\d{8}T\d\d:\d\d:\d\d$/}, 'as_dateTime'],
string => [40, sub {1}, 'as_string'],
},
attr => {},
namespaces => {},
@_,
);
}
sub envelope {
my $self = shift;
$self = $self->new() if not ref $self; # serves a method call if object
my $type = shift;
my $body;
if ($type eq 'response') {
# shift off method name to make XMLRPT happy
my $method = shift
or die "Unspecified method for XMLRPC call\n";
$body = XMLRPC::Data->name( methodResponse => \XMLRPC::Data->value(
XMLRPC::Data->type(params => [@_])
)
);
}
elsif ($type eq 'method') {
# shift off method name to make XMLRPT happy
my $method = shift
or die "Unspecified method for XMLRPC call\n";
$body = XMLRPC::Data->name( methodCall => \XMLRPC::Data->value(
XMLRPC::Data->type(
methodName => UNIVERSAL::isa($method => 'XMLRPC::Data')
? $method->name
: $method
),
XMLRPC::Data->type(params => [@_])
));
}
elsif ($type eq 'fault') {
$body = XMLRPC::Data->name(methodResponse =>
\XMLRPC::Data->type(fault => {faultCode => $_[0], faultString => $_[1]}),
);
}
else {
die "Wrong type of envelope ($type) for XMLRPC call\n";
}
# SOAP::Lite keeps track of objects for XML aliasing and multiref
# encoding.
# Set/reset seen() hashref before/after encode_object avoids a
# memory leak
$self->seen({}); # initialize multiref table
my $envelope = $self->xmlize($self->encode_object($body));
$self->seen({}); # delete multi-ref table - avoids a memory hole...
return $envelope;
}
sub encode_object {
my $self = shift;
my @encoded = $self->SUPER::encode_object(@_);
return $encoded[0]->[0] =~ /^(?:array|struct|i4|int|boolean|string|double|dateTime\.iso8601|base64)$/o
? ['value', {}, [@encoded]]
: @encoded;
}
sub encode_scalar {
my $self = shift;
return ['value', {}] unless defined $_[0];
return $self->SUPER::encode_scalar(@_);
}
sub encode_array {
my ($self, $array) = @_;
return ['array', {}, [
['data', {}, [ map {$self->encode_object($_)} @{ $array } ] ]
]];
}
sub encode_hash {
my ($self, $hash) = @_;
return ['struct', {}, [
map {
['member', {}, [['name', {}, SOAP::Utils::encode_data($_)], $self->encode_object($hash->{$_})]]
} keys %{ $hash }
]];
}
sub as_methodName {
my ($self, $value, $name, $type, $attr) = @_;
return [ 'methodName', $attr, $value ];
}
sub as_params {
my ($self, $params, $name, $type, $attr) = @_;
return ['params', $attr, [
map {
['param', {}, [ $self->encode_object($_) ] ]
} @$params
]];
}
sub as_fault {
my ($self, $fault) = @_;
return ['fault', {}, [ $self->encode_object($fault) ] ];
}
sub BEGIN {
no strict 'refs';
for my $type (qw(double i4 int)) {
my $method = 'as_' . $type;
*$method = sub {
my($self, $value) = @_;
return [ $type, {}, $value ];
}
}
}
sub as_base64 {
my ($self, $value) = @_;
require MIME::Base64;
return ['base64', {}, MIME::Base64::encode_base64($value,'')];
}
sub as_string {
my ($self, $value) = @_;
return ['string', {}, SOAP::Utils::encode_data($value)];
}
sub as_dateTime {
my ($self, $value) = @_;
return ['dateTime.iso8601', {}, $value];
}
sub as_boolean {
my ($self, $value) = @_;
return ['boolean', {}, $value ? 1 : 0];
}
sub typecast {
my ($self, $value, $name, $type, $attr) = @_;
die "Wrong/unsupported datatype '$type' specified\n" if defined $type;
$self->SUPER::typecast(@_);
}
# ======================================================================
package XMLRPC::SOM;
@XMLRPC::SOM::ISA = qw(SOAP::SOM);
sub BEGIN {
no strict 'refs';
my %path = (
root => '/',
envelope => '/[1]',
method => '/methodCall/methodName',
fault => '/methodResponse/fault',
);
for my $method (keys %path) {
*$method = sub {
my $self = shift;
ref $self or return $path{$method};
Carp::croak "Method '$method' is readonly and doesn't accept any parameters" if @_;
$self->valueof($path{$method});
};
}
my %fault = (
faultcode => 'faultCode',
faultstring => 'faultString',
);
for my $method (keys %fault) {
*$method = sub {
my $self = shift;
ref $self or Carp::croak "Method '$method' doesn't have shortcut";
Carp::croak "Method '$method' is readonly and doesn't accept any parameters" if @_;
defined $self->fault ? $self->fault->{$fault{$method}} : undef;
};
}
my %results = (
result => '/methodResponse/params/[1]',
paramsin => '/methodCall/params/param',
paramsall => '/methodResponse/params/param',
);
for my $method (keys %results) {
*$method = sub {
my $self = shift;
ref $self or return $results{$method};
Carp::croak "Method '$method' is readonly and doesn't accept any parameters" if @_;
defined $self->fault()
? undef
: $self->valueof($results{$method});
};
}
}
# ======================================================================
package XMLRPC::Deserializer;
@XMLRPC::Deserializer::ISA = qw(SOAP::Deserializer);
BEGIN {
no strict 'refs';
for my $method (qw(o_child o_qname o_chars)) { # import from SOAP::Utils
*$method = \&{'SOAP::Utils::'.$method};
}
}
sub deserialize {
# just deserialize with SOAP::Lite's deserializer, and re-bless as
# XMLRPC::SOM
bless shift->SUPER::deserialize(@_) => 'XMLRPC::SOM';
}
sub decode_value {
my $self = shift;
my $ref = shift;
my($name, $attrs, $children, $value) = @$ref;
if ($name eq 'value') {
$children ? scalar(($self->decode_object($children->[0]))[1]) : $value;
}
elsif ($name eq 'array') {
return [map {scalar(($self->decode_object($_))[1])} @{o_child($children->[0]) || []}];
}
elsif ($name eq 'struct') {
return {
map {
my %hash = map { o_qname($_) => $_ } @{o_child($_) || []};
# v----- scalar is required here, because 5.005 evaluates 'undef' in list context as empty array
(o_chars($hash{name}) => scalar(($self->decode_object($hash{value}))[1]));
} @{$children || []}};
}
elsif ($name eq 'base64') {
require MIME::Base64;
MIME::Base64::decode_base64($value);
}
elsif ($name =~ /^(?:int|i4|boolean|string|double|dateTime\.iso8601|methodName)$/) {
return $value;
}
elsif ($name =~ /^(?:params)$/) {
return [map {scalar(($self->decode_object($_))[1])} @{$children || []}];
}
elsif ($name =~ /^(?:methodResponse|methodCall)$/) {
return +{map {$self->decode_object($_)} @{$children || []}};
}
elsif ($name =~ /^(?:param|fault)$/) {
return scalar(($self->decode_object($children->[0]))[1]);
}
elsif ($name =~ /^(?:nil)$/) {
return undef;
}
else {
die "wrong element '$name'\n";
}
}
# ======================================================================
package XMLRPC::Server;
@XMLRPC::Server::ISA = qw(SOAP::Server);
sub initialize {
return (
deserializer => XMLRPC::Deserializer->new,
serializer => XMLRPC::Serializer->new,
on_action => sub {},
on_dispatch => sub { return map {s!\.!/!g; $_} shift->method =~ /^(?:(.*)\.)?(\w+)$/ },
);
}
# ======================================================================
package XMLRPC::Server::Parameters;
@XMLRPC::Server::Parameters::ISA = qw(SOAP::Server::Parameters);
# ======================================================================
package XMLRPC;
@XMLRPC::ISA = qw(SOAP);
# ======================================================================
package XMLRPC::Lite;
@XMLRPC::Lite::ISA = qw(SOAP::Lite);
sub new {
my $class = shift;
return $class if ref $class;
return $class->SUPER::new(
serializer => XMLRPC::Serializer->new,
deserializer => XMLRPC::Deserializer->new,
on_action => sub {return},
default_ns => 'http://unspecified/',
@_
);
}
# ======================================================================
1;
__END__
=head1 NAME
XMLRPC::Lite - client and server implementation of XML-RPC protocol
=head1 SYNOPSIS
=over 4
=item Client
use XMLRPC::Lite;
print XMLRPC::Lite
-> proxy('http://betty.userland.com/RPC2')
-> call('examples.getStateStruct', {state1 => 12, state2 => 28})
-> result;
=item CGI server
use XMLRPC::Transport::HTTP;
my $server = XMLRPC::Transport::HTTP::CGI
-> dispatch_to('methodName')
-> handle
;
=item Daemon server
use XMLRPC::Transport::HTTP;
my $daemon = XMLRPC::Transport::HTTP::Daemon
-> new (LocalPort => 80)
-> dispatch_to('methodName')
;
print "Contact to XMLRPC server at ", $daemon->url, "\n";
$daemon->handle;
=back
=head1 DESCRIPTION
XMLRPC::Lite is a Perl modules which provides a simple nterface to the
XML-RPC protocol both on client and server side. Based on SOAP::Lite module,
it gives you access to all features and transports available in that module.
See F<t/26-xmlrpc.t> for client examples and F<examples/XMLRPC/*> for server
implementations.
=head1 DEPENDENCIES
SOAP::Lite
=head1 SEE ALSO
SOAP::Lite
=head1 CREDITS
The B<XML-RPC> standard is Copyright (c) 1998-2001, UserLand Software, Inc.
See <http://www.xmlrpc.com> for more information about the B<XML-RPC>
specification.
=head1 COPYRIGHT
Copyright (C) 2000-2001 Paul Kulchenko. All rights reserved.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 AUTHOR
Paul Kulchenko (paulclinger@yahoo.com)
=cut