File Coverage

lib/XML/RPC/Fast.pm
Criterion Covered Total %
statement 27 145 18.6
branch 2 62 3.2
condition 0 44 0.0
subroutine 8 20 40.0
pod 8 8 100.0
total 45 279 16.1


line stmt bran cond sub pod time code
1             # XML::RPC::Fast
2             #
3             # Copyright (c) 2008-2009 Mons Anderson , all rights reserved
4             # This program is free software; you can redistribute it and/or
5             # modify it under the same terms as Perl itself.
6              
7             package XML::RPC::Fast;
8              
9             =head1 NAME
10              
11             XML::RPC::Fast - Fast and modular implementation for an XML-RPC client and server
12              
13             =cut
14              
15             our $VERSION = '0.8'; $VERSION = eval $VERSION;
16              
17             =head1 SYNOPSIS
18              
19             Generic usage
20              
21             use XML::RPC::Fast;
22            
23             my $server = XML::RPC::Fast->new( undef, %args );
24             my $client = XML::RPC::Fast->new( $uri, %args );
25              
26             Create a simple XML-RPC service:
27              
28             use XML::RPC::Fast;
29            
30             my $rpc = XML::RPC::Fast->new(
31             undef, # the url is not required by server
32             external_encoding => 'koi8-r', # any encoding, accepted by Encode
33             #internal_encoding => 'koi8-r', # not supported for now
34             );
35             my $xml = do { local $/; };
36             length($xml) == $ENV{CONTENT_LENGTH} or warn "Content-Length differs from actually received";
37            
38             print "Content-type: text/xml; charset=$rpc->{external_encoding}\n\n";
39             print $rpc->receive( $xml, sub {
40             my ( $methodname, @params ) = @_;
41             return { you_called => $methodname, with_params => \@params };
42             } );
43              
44             Make a call to an XML-RPC service:
45              
46             use XML::RPC::Fast;
47            
48             my $rpc = XML::RPC::Fast->new(
49             'http://your.hostname/rpc/url'
50             );
51            
52             # Syncronous call
53             my @result = $rpc->req(
54             call => [ 'examples.getStateStruct', { state1 => 12, state2 => 28 } ],
55             url => 'http://...',
56             );
57            
58             # Syncronous call (compatibility method)
59             my @result = $rpc->call( 'examples.getStateStruct', { state1 => 12, state2 => 28 } );
60            
61             # Syncronous or asyncronous call
62             $rpc->req(
63             call => ['examples.getStateStruct', { state1 => 12, state2 => 28 }],
64             cb => sub {
65             my @result = @_;
66             },
67             );
68            
69             # Syncronous or asyncronous call (compatibility method)
70             $rpc->call( sub {
71             my @result = @_;
72            
73             }, 'examples.getStateStruct', { state1 => 12, state2 => 28 } );
74            
75              
76             =head1 DESCRIPTION
77              
78             XML::RPC::Fast is format-compatible with XML::RPC, but may use different encoders to parse/compose xml.
79             Curerntly included encoder uses L, and is 3 times faster than XML::RPC and 75% faster, than XML::Parser implementation
80              
81             =head1 METHODS
82              
83             =head2 new ($url, %args)
84              
85             Create XML::RPC::Fast object, server if url is undef, client if url is defined
86              
87             =head2 req( %ARGS )
88              
89             Clientside. Make syncronous or asyncronous call (depends on UA).
90              
91             If have cb, will invoke $cb with results and should not croak
92              
93             If have no cb, will return results and croak on error (only syncronous UA)
94              
95             Arguments are
96              
97             =over 4
98              
99             =item call => [ methodName => @args ]
100              
101             array ref of call arguments. Required
102              
103             =item cb => $cb->(@results)
104              
105             Invocation callback. Optional for syncronous UA. Behaviour is same as in call with C<$cb> and without
106              
107             =item url => $request_url
108              
109             Alternative invocation URL. Optional. By default will be used defined from constructor
110              
111             =item headers => { http-headers hashref }
112              
113             Additional http headers to request
114              
115             =item external_encoding => '...,
116              
117             Specify the encoding, used inside XML container just for this request. Passed to encoder
118              
119             =back
120              
121             =head2 call( 'method_name', @arguments ) : @results
122              
123             Clientside. Make syncronous call and return results. Croaks on error. Just a simple wrapper around C
124              
125             =head2 call( $cb->(@res), 'method_name', @arguments ): void
126              
127             Clientside. Make syncronous or asyncronous call (depends on UA) and invoke $cb with results. Should not croak. Just a simple wrapper around C
128              
129             =head2 receive ( $xml, $handler->($methodName,@args) ) : xml byte-stream
130              
131             Serverside. Process received XML and invoke $handler with parameters $methodName and @args and returns response XML
132              
133             On error conditions C<$handler> could set C<$XML::RPC::Fast::faultCode> and die, or return C
134              
135             ->receive( $xml, sub {
136             # ...
137             return rpcfault( 3, "Some error" ) if $error_condition
138             $XML::RPC::Fast::faultCode = 4 and die "Another error" if $another_error_condition;
139              
140             return { call => $methodname, params => \@params };
141             })
142              
143             =head2 registerType
144              
145             Proxy-method to encoder. See L
146              
147             =head2 registerClass
148              
149             Proxy-method to encoder. See L
150              
151             =head1 OPTIONS
152              
153             Below is the options, accepted by new()
154              
155             =head2 ua
156              
157             Client only. Useragent object, or package name
158              
159             ->new( $url, ua => 'LWP' ) # same as XML::RPC::UA::LWP
160             # or
161             ->new( $url, ua => 'XML::RPC::UA::LWP' )
162             # or
163             ->new( $url, ua => XML::RPC::UA::LWP->new( ... ) )
164             # or
165             ->new( $url, ua => XML::RPC::UA::Curl->new( ... ) )
166              
167             =head2 timeout
168              
169             Client only. Timeout for calls. Passed directly to UA
170              
171             ->new( $url, ua => 'LWP', timeout => 10 )
172              
173             =head2 useragent
174              
175             Client only. Useragent string. Passed directly to UA
176              
177             ->new( $url, ua => 'LWP', useragent => 'YourClient/1.11' )
178              
179             =head2 encoder
180              
181             Client and server. Encoder object or package name
182              
183             ->new( $url, encoder => 'LibXML' )
184             # or
185             ->new( $url, encoder => 'XML::RPC::Enc::LibXML' )
186             # or
187             ->new( $url, encoder => XML::RPC::Enc::LibXML->new( ... ) )
188              
189             =head2 internal_encoding B
190              
191             Specify the encoding you are using in your code. By default option is undef, which means flagged utf-8
192             For translations is used Encode, so the list of accepted encodings fully derived from it.
193              
194             =head2 external_encoding
195              
196             Specify the encoding, used inside XML container. By default it's utf-8. Passed directly to encoder
197              
198             ->new( $url, encoder => 'LibXML', external_encoding => 'koi8-r' )
199              
200             =head1 ACCESSORS
201              
202             =head2 url
203              
204             Get or set client url
205              
206             =head2 encoder
207              
208             Direct access to encoder object
209              
210             =head2 ua
211              
212             Direct access to useragent object
213              
214             =head1 FUNCTIONS
215              
216             =head2 rpcfault(faultCode, faultString)
217              
218             Returns hash structure, that may be returned by serverside handler, instead of die. Not exported by default
219              
220             =head1 CUSTOM TYPES
221              
222             =head2 sub {{ 'base64' => encode_base64($data) }}
223              
224             When passing a CODEREF as a value, encoder will simply use the returned hashref as a type => value pair.
225              
226             =head2 bless( do{\(my $o = encode_base64('test') )}, 'base64' )
227              
228             When passing SCALARREF as a value, package name will be taken as type and dereference as a value
229              
230             =head2 bless( do{\(my $o = { something =>'complex' } )}, 'base64' )
231              
232             When passing REFREF as a value, package name will be taken as type and LC<::hash2xml(deref)> would be used as value
233              
234             =head2 customtype( $type, $data )
235              
236             Easily compose SCALARREF based custom type
237              
238             =cut
239              
240 3     3   52287 use 5.008003; # I want Encode to work
  3         10  
  3         158  
241 3     3   19 use strict;
  3         6  
  3         105  
242 3     3   16 use warnings;
  3         20  
  3         118  
243              
244             #use Time::HiRes qw(time);
245 3     3   100 use Carp qw(carp croak);
  3         6  
  3         426  
246              
247             BEGIN {
248             eval {
249 3         2931 require Sub::Name;
250 3         3006 Sub::Name->import('subname');
251 3 50   3   6 1 } or do { *subname = sub { $_[1] } };
  3         17  
  0         0  
  0         0  
252              
253 3     3   21 no strict 'refs';
  3         6  
  3         324  
254 3         8 for my $m (qw(url encoder ua)) {
255             *$m = sub {
256 0     0   0 local *__ANON__ = $m;
257 0         0 my $self = shift;
258 0 0       0 $self->{$m} = shift if @_;
259 0         0 $self->{$m};
260 9         230 };
261             }
262             }
263              
264             our $faultCode = 0;
265              
266             #sub encoder { shift->{encoder} }
267             #sub ua { shift->{ua} }
268              
269             sub import {
270 2     2   19 my $me = shift;
271 2         6 my $pkg = caller;
272 3     3   17 no strict 'refs';
  3         5  
  3         7573  
273 2 50       38 @_ or return;
274 0           for (@_) {
275 0 0 0       if ( $_ eq 'rpcfault' or $_ eq 'customtype') {
276 0           *{$pkg.'::'.$_} = \&$_;
  0            
277             } else {
278 0           croak "$_ is not exported by $me";
279             }
280             }
281             }
282              
283             sub rpcfault($$) {
284 0     0 1   my ($code,$string) = @_;
285             return {
286 0           fault => {
287             faultCode => $code,
288             faultString => $string,
289             },
290             }
291             }
292             sub customtype($$) {
293 0     0 1   my $type = shift;
294 0           my $data = shift;
295 0           bless( do{\(my $o = $data )}, $type )
  0            
296             }
297              
298             sub _load {
299 0     0     my $pkg = shift;
300 0           my ($prefix,$req,$default,@args) = @_;
301 0 0         if (defined $req) {
302 0           my @fail;
303             eval {
304 0           require join '/', split '::', $prefix.$req.'.pm';
305 0           $req = $prefix.$req;
306 0           1;
307             }
308             or do {
309 0           push @fail, [ $prefix.$req,$@ ];
310 0           eval{ require join '/', split '::', $req.'.pm'; 1 }
  0            
  0            
311             }
312 0 0 0       or do {
313 0           push @fail, [ $req,$@ ];
314 0           croak "Can't load any of:\n".join("\n\t",map { "$$_[0]: $$_[1]" } @fail)."\n";
  0            
315             }
316             } else {
317             eval {
318 0           $req = $prefix.$default;
319 0           require join '/', split '::', $req.'.pm'; 1
  0            
320             }
321 0 0         or do {
322 0           croak "Can't load $req: $@\n";
323             }
324             }
325 0           return $req->new(@args);
326             }
327              
328             sub new {
329 0     0 1   my $package = shift;
330 0           my $url = shift;
331 0     0     local $SIG{__WARN__} = sub { local $_ = shift; s{\n$}{};carp $_ };
  0            
  0            
  0            
332 0           my $self = {
333             @_,
334             };
335 0 0         unless ( ref $self->{encoder} ) {
336 0           $self->{encoder} = $package->_load(
337             'XML::RPC::Enc::', $self->{encoder}, 'LibXML',
338             internal_encoding => $self->{internal_encoding},
339             external_encoding => $self->{external_encoding},
340             );
341             }
342 0 0 0       if ( $url and !ref $self->{ua} ) {
343 0   0       $self->{ua} = $package->_load(
344             'XML::RPC::UA::', $self->{ua}, 'LWP',
345             ua => $self->{useragent} || 'XML-RPC-Fast/'.$VERSION,
346             timeout => $self->{timeout},
347             );
348             }
349 0           $self->{url} = $url;
350 0           bless $self, $package;
351 0           return $self;
352             }
353              
354             sub registerType {
355 0     0 1   shift->encoder->registerType(@_);
356             }
357              
358             sub registerClass {
359 0     0 1   shift->encoder->registerClass(@_);
360             }
361              
362             sub call {
363 0     0 1   my $self = shift;
364 0 0 0       my $cb;$cb = shift if ref $_[0] and ref $_[0] eq 'CODE';
  0            
365 0 0         $self->req(
366             call => [@_],
367             $cb ? ( cb => $cb ) : (),
368             );
369             }
370              
371             sub req {
372 0     0 1   my $self = shift;
373 0           my %args = @_;
374 0           my $cb = $args{cb};
375 0 0 0       if ($self->ua->async and !$cb) {
376 0           croak("Call have no cb and useragent is async");
377             }
378 0           my ( $methodname, @params ) = @{ $args{call} };
  0            
379 0   0       my $url = $args{url} || $self->{url};
380              
381 0 0         unless ( $url ) {
382 0 0         if ($cb) {
383 0           $cb->(rpcfault(500, "No url"));
384 0           return;
385             } else {
386 0           croak('No url');
387             }
388             };
389 0           my $uri = "$url#$methodname";
390              
391 0           $faultCode = 0;
392 0           my $body;
393             {
394 0 0         local $self->encoder->{external_encoding} = $args{external_encoding} if exists $args{external_encoding};
  0            
395 0           my $newurl;
396 0           ($body,$newurl) = $self->encoder->request( $methodname, @params );
397 0 0         $url = $newurl if defined $newurl;
398             }
399              
400 0           $self->{xml_out} = $body;
401              
402             #my $start = time;
403 0           my @data;
404             #warn "Call $body";
405             $self->ua->call(
406             ($args{method} || 'POST') => $url,
407             $args{headers} ? ( headers => $args{headers} ) : (),
408             body => $body,
409             cb => sub {
410 0     0     my $res = shift;
411             {
412 0           ( my $status = $res->status_line )=~ s/:?\s*$//s;
  0            
413 0 0 0       $res->code == 200 or @data =
414             (rpcfault( $res->code, "Call to $uri failed: $status" ))
415             and last;
416 0           my $text = $res->content;
417 0 0 0       length($text) and $text =~ /^\s*<\?xml/s or @data =
      0        
418             ({fault=>{ faultCode => 499, faultString => "Call to $uri failed: Response is not an XML: \"$text\"" }})
419             and last;
420 0 0 0       eval {
421 0           $self->{xml_in} = $text;
422 0           @data = $self->encoder->decode( $text );
423 0           1;
424             } or @data =
425             ({fault=>{ faultCode => 499, faultString => "Call to $uri failed: Bad Response: $@, \"$text\"" }})
426             and last;
427             }
428             #warn "Have data @data";
429 0 0 0       if ($cb) {{
  0 0          
430 0           local $faultCode = $data[0]{fault}{faultCode} if ref $data[0] eq 'HASH' and exists $data[0]{fault};
431 0           $cb->(@data);
432 0           return;
433             }}
434             },
435 0 0 0       );
436 0 0 0       $cb and defined wantarray and carp "Useless use of return value for ".__PACKAGE__."->call(cb)";
437 0 0         return if $cb;
438 0 0 0       if ( ref $data[0] eq 'HASH' and exists $data[0]{fault} ) {
439 0           $faultCode = $data[0]{fault}{faultCode};
440 0           croak( "Remote Error [$data[0]{fault}{faultCode}]: ".$data[0]{fault}{faultString} );
441             }
442 0 0         return @data == 1 ? $data[0] : @data;
443             }
444              
445             sub receive { # ok
446 0     0 1   my $self = shift;
447 0           my $result = eval {
448 0 0         my $xml_in = shift or return $self->encoder->fault(400,"Bad Request: No XML");
449 0 0         my $handler = shift or return $self->encoder->fault(501,"Server Error: No handler");;
450 0           my ( $methodname, @params ) = $self->encoder->decode($xml_in);
451 0           local $self->{xml_in} = $xml_in;
452 0           subname( 'receive.handler.'.$methodname,$handler );
453 0           my @res = $handler->( $methodname, @params );
454 0 0 0       if (ref $res[0] eq 'HASH' and exists $res[0]{fault}) {
455 0           $self->encoder->fault( $res[0]{fault}{faultCode},$res[0]{fault}{faultString} );
456             } else {
457 0           $self->encoder->response( @res );
458             }
459             };
460 0 0         if ($@) {
461 0           (my $e = "$@") =~ s{\r?\n+$}{}s;
462 0 0         $result = $self->encoder->fault(defined $faultCode ? $faultCode : 500,$e);
463             }
464 0           return $result;
465             }
466              
467             =head1 BUGS & SUPPORT
468              
469             Bugs reports and testcases are welcome.
470              
471             It you write your own Enc or UA, I may include it into distribution
472              
473             If you have propositions for default custom types (see Enc), send me patches
474              
475             See L to report and view bugs.
476              
477             =head1 AUTHOR
478              
479             Mons Anderson, C<< >>
480              
481             =head1 COPYRIGHT & LICENSE
482              
483             Copyright (c) 2008-2009 Mons Anderson.
484              
485             This program is free software; you can redistribute it and/or modify it
486             under the same terms as Perl itself.
487              
488             =cut
489              
490             1;