File Coverage

blib/lib/XML/RPC.pm
Criterion Covered Total %
statement 18 119 15.1
branch 0 32 0.0
condition 0 14 0.0
subroutine 5 23 21.7
pod 5 19 26.3
total 28 207 13.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             XML::RPC -- Pure Perl implementation for an XML-RPC client and server.
5              
6             =head1 SYNOPSIS
7              
8             make a call to an XML-RPC server:
9              
10             use XML::RPC;
11              
12             my $xmlrpc = XML::RPC->new('http://betty.userland.com/RPC2');
13             my $result = $xmlrpc->call( 'examples.getStateStruct', { state1 => 12, state2 => 28 } );
14              
15             create an XML-RPC service:
16              
17             use XML::RPC;
18             use CGI;
19              
20             my $q = new CGI;
21             my $xmlrpc = XML::RPC->new();
22             my $xml = $q->param('POSTDATA');
23              
24             print $q->header( -type => 'text/xml', -charset => 'UTF-8' );
25             print $xmlrpc->receive( $xml, \&handler );
26              
27             sub handler {
28             my ( $methodname, @params ) = @_;
29             return { you_called => $methodname, with_params => \@params };
30             }
31              
32             =head1 DESCRIPTION
33              
34             XML::RPC module provides simple Pure Perl methods for XML-RPC communication.
35             It's goals are simplicity and flexibility. XML::RPC uses XML::TreePP
36             for parsing.
37              
38             =head1 CONSTRUCTOR AND OPTIONS
39              
40             =head2 $xmlrpc = XML::RPC->new();
41              
42             This constructor method returns a new XML::RPC object. Usable for XML-RPC servers.
43              
44             =head2 $xmlrpc = XML::RPC->new( 'http://betty.userland.com/RPC2', %options );
45              
46             Its first argument is the full URL for your server. The second argument
47             is for options passing to XML::TreePP, for example: output_encoding => 'ISO-8859-1'
48             (default is UTF-8).
49              
50             =head1 METHODS
51              
52             =head2 $xmlrpc->call( 'method_name', @arguments );
53              
54             This method calls the provides XML-RPC server's method_name with
55             @arguments. It will return the server method's response.
56              
57             =head2 $xmlrpc->receive( $xml, \&handler );
58              
59             This parses an incoming XML-RPC methodCall and call the \&handler subref
60             with parameters: $methodName and @parameters.
61              
62             =head2 $xmlrpc->xml_in();
63              
64             Returns the last XML that went in the client.
65              
66             =head2 $xmlrpc->xml_out();
67              
68             Returns the last XML that went out the client.
69              
70             =head1 CUSTOM TYPES
71              
72             =head2 $xmlrpc->call( 'method_name', { data => sub { { 'base64' => encode_base64($data) } } } );
73              
74             When passing a CODEREF to a value XML::RPC will simply use the returned hashref as a type => value pair.
75              
76             =head1 ERROR HANDLING
77              
78             To provide an error response you can simply die() in the \&handler
79             function. Also you can set the $XML::RPC::faultCode variable to a (int) value
80             just before dieing.
81              
82             =head1 PROXY SUPPORT
83              
84             Default XML::RPC will try to use LWP::Useragent for requests,
85             you can set the environment variable: CGI_HTTP_PROXY to
86             set a proxy.
87              
88             =head1 LIMITATIONS
89              
90             XML::RPC will not create "bool", "dateTime.iso8601" or "base64" types
91             automatically. They will be parsed as "int" or "string". You can use the
92             CODE ref to create these types.
93              
94             =head1 AUTHOR
95              
96             Niek Albers, http://www.daansystems.com/
97              
98             =head1 COPYRIGHT AND LICENSE
99              
100             Copyright (c) 2007-2008 Niek Albers. All rights reserved. This program
101             is free software; you can redistribute it and/or modify it under the same
102             terms as Perl itself.
103              
104             =cut
105              
106             package XML::RPC;
107              
108 2     2   51183 use strict;
  2         5  
  2         86  
109 2     2   3440 use XML::TreePP;
  2         35779  
  2         89  
110 2     2   30 use vars qw($VERSION $faultCode);
  2         10  
  2         140  
111 2     2   11 no strict 'refs';
  2         5  
  2         3162  
112              
113             $VERSION = 0.9;
114             $faultCode = 0;
115              
116             sub new {
117 1     1 1 15 my $package = shift;
118 1         4 my $self = {};
119 1         3 bless $self, $package;
120 1         8 $self->{url} = shift;
121 1         10 $self->{tpp} = XML::TreePP->new(@_);
122 1         10 return $self;
123             }
124              
125             sub call {
126 0     0 1   my $self = shift;
127 0           my ( $methodname, @params ) = @_;
128              
129 0 0         die 'no url' if ( !$self->{url} );
130              
131 0           $faultCode = 0;
132 0           my $xml_out = $self->create_call_xml( $methodname, @params );
133              
134 0           $self->{xml_out} = $xml_out;
135              
136 0           my ( $result, $xml_in ) = $self->{tpp}->parsehttp(
137             POST => $self->{url},
138             $xml_out,
139             {
140             'Content-Type' => 'text/xml',
141             'User-Agent' => 'XML-RPC/' . $VERSION,
142             'Content-Length' => length($xml_out)
143             }
144             );
145              
146 0           $self->{xml_in} = $xml_in;
147              
148 0           my @data = $self->unparse_response($result);
149 0 0         return @data == 1 ? $data[0] : @data;
150             }
151              
152             sub receive {
153 0     0 1   my $self = shift;
154 0           my $result = eval {
155 0   0       my $xml_in = shift || die 'no xml';
156 0           $self->{xml_in} = $xml_in;
157 0   0       my $handler = shift || die 'no handler';
158 0           my $hash = $self->{tpp}->parse($xml_in);
159 0           my ( $methodname, @params ) = $self->unparse_call($hash);
160 0           $self->create_response_xml( $handler->( $methodname, @params ) );
161             };
162              
163 0 0         $result = $self->create_fault_xml($@) if ($@);
164 0           $self->{xml_out} = $result;
165 0           return $result;
166              
167             }
168              
169             sub create_fault_xml {
170 0     0 0   my $self = shift;
171 0           my $error = shift;
172 0           chomp($error);
173 0           return $self->{tpp}
174             ->write( { methodResponse => { fault => $self->parse( { faultString => $error, faultCode => int($faultCode) } ) } } );
175             }
176              
177             sub create_call_xml {
178 0     0 0   my $self = shift;
179 0           my ( $methodname, @params ) = @_;
180              
181 0           return $self->{tpp}->write(
182             {
183             methodCall => {
184             methodName => $methodname,
185 0           params => { param => [ map { $self->parse($_) } @params ] }
186             }
187             }
188             );
189             }
190              
191             sub create_response_xml {
192 0     0 0   my $self = shift;
193 0           my @params = @_;
194              
195 0           return $self->{tpp}->write( { methodResponse => { params => { param => [ map { $self->parse($_) } @params ] } } } );
  0            
196             }
197              
198             sub parse {
199 0     0 0   my $self = shift;
200 0           my $p = shift;
201 0           my $result;
202              
203 0 0         if ( ref($p) eq 'HASH' ) {
    0          
    0          
204 0           $result = $self->parse_struct($p);
205             }
206             elsif ( ref($p) eq 'ARRAY' ) {
207 0           $result = $self->parse_array($p);
208             }
209             elsif ( ref($p) eq 'CODE' ) {
210 0           $result = $p->();
211             }
212             else {
213 0           $result = $self->parse_scalar($p);
214             }
215              
216 0           return { value => $result };
217             }
218              
219             sub parse_scalar {
220 0     0 0   my $self = shift;
221 0           my $scalar = shift;
222 0           local $^W = undef;
223              
224 0 0 0       if ( ( $scalar =~ m/^[\-+]?\d+$/ )
    0          
225             && ( abs($scalar) <= ( 0xffffffff >> 1 ) ) )
226             {
227 0           return { i4 => $scalar };
228             }
229             elsif ( $scalar =~ m/^[\-+]?\d+\.\d+$/ ) {
230 0           return { double => $scalar };
231             }
232             else {
233 0           return { string => \$scalar };
234             }
235             }
236              
237             sub parse_struct {
238 0     0 0   my $self = shift;
239 0           my $hash = shift;
240              
241 0           return { struct => { member => [ map { { name => $_, %{ $self->parse( $hash->{$_} ) } } } keys(%$hash) ] } };
  0            
  0            
242             }
243              
244             sub parse_array {
245 0     0 0   my $self = shift;
246 0           my $array = shift;
247              
248 0           return { array => { data => { value => [ map { $self->parse($_)->{value} } $self->list($array) ] } } };
  0            
249             }
250              
251             sub unparse_response {
252 0     0 0   my $self = shift;
253 0           my $hash = shift;
254              
255 0   0       my $response = $hash->{methodResponse} || die 'no data';
256              
257 0 0         if ( $response->{fault} ) {
258 0           return $self->unparse_value( $response->{fault}->{value} );
259             }
260             else {
261 0           return map { $self->unparse_value( $_->{value} ) } $self->list( $response->{params}->{param} );
  0            
262             }
263             }
264              
265             sub unparse_call {
266 0     0 0   my $self = shift;
267 0           my $hash = shift;
268              
269 0   0       my $response = $hash->{methodCall} || die 'no data';
270              
271 0           my $methodname = $response->{methodName};
272 0           my @args =
273 0           map { $self->unparse_value( $_->{value} ) } $self->list( $response->{params}->{param} );
274 0           return ( $methodname, @args );
275             }
276              
277             sub unparse_value {
278 0     0 0   my $self = shift;
279 0           my $value = shift;
280 0           my $result;
281              
282 0 0         return $value if ( ref($value) ne 'HASH' ); # for unspecified params
283 0 0         if ( $value->{struct} ) {
    0          
284 0           $result = $self->unparse_struct( $value->{struct} );
285 0 0         return !%$result
286             ? undef
287             : $result; # fix for empty hashrefs from XML::TreePP
288             }
289             elsif ( $value->{array} ) {
290 0           return $self->unparse_array( $value->{array} );
291             }
292             else {
293 0           return $self->unparse_scalar($value);
294             }
295             }
296              
297             sub unparse_scalar {
298 0     0 0   my $self = shift;
299 0           my $scalar = shift;
300 0           my ($result) = values(%$scalar);
301 0 0 0       return ( ref($result) eq 'HASH' && !%$result )
302             ? undef
303             : $result; # fix for empty hashrefs from XML::TreePP
304             }
305              
306             sub unparse_struct {
307 0     0 0   my $self = shift;
308 0           my $struct = shift;
309              
310 0           return { map { $_->{name} => $self->unparse_value( $_->{value} ) } $self->list( $struct->{member} ) };
  0            
311             }
312              
313             sub unparse_array {
314 0     0 0   my $self = shift;
315 0           my $array = shift;
316 0           my $data = $array->{data};
317              
318 0           return [ map { $self->unparse_value($_) } $self->list( $data->{value} ) ];
  0            
319             }
320              
321             sub list {
322 0     0 0   my $self = shift;
323 0           my $param = shift;
324 0 0         return () if ( !$param );
325 0 0         return @$param if ( ref($param) eq 'ARRAY' );
326 0           return ($param);
327             }
328              
329 0     0 1   sub xml_in { shift->{xml_in} }
330              
331 0     0 1   sub xml_out { shift->{xml_out} }
332              
333             1;