File Coverage

blib/lib/Hessian/Tiny/Client.pm
Criterion Covered Total %
statement 88 109 80.7
branch 18 42 42.8
condition 9 20 45.0
subroutine 16 16 100.0
pod 2 2 100.0
total 133 189 70.3


line stmt bran cond sub pod time code
1             package Hessian::Tiny::Client;
2              
3 2     2   50064 use warnings;
  2         4  
  2         60  
4 2     2   12 use strict;
  2         2  
  2         72  
5              
6             require 5.006;
7              
8 2     2   1846 use URI ();
  2         34121  
  2         54  
9 2     2   2249 use IO::File ();
  2         26618  
  2         50  
10 2     2   2483 use LWP::UserAgent ();
  2         178225  
  2         56  
11 2     2   25 use HTTP::Headers ();
  2         4  
  2         31  
12 2     2   9 use HTTP::Request ();
  2         4  
  2         28  
13 2     2   15997 use File::Temp ();
  2         73347  
  2         65  
14              
15 2     2   2066 use Hessian::Tiny::ConvertorV1 ();
  2         11  
  2         60  
16 2     2   1423 use Hessian::Tiny::ConvertorV2 ();
  2         8  
  2         3049  
17              
18             =head1 NAME
19              
20             Hessian::Tiny::Client - Hessian RPC Client implementation in pure Perl
21              
22             =head1 VERSION
23              
24             Version 0.12
25              
26             =cut
27              
28             our $VERSION = '0.12';
29             our $Error;
30              
31              
32             =head1 SYNOPSIS
33              
34             use Hessian::Tiny::Client;
35              
36             my $foo = Hessian::Tiny::Client->new(
37             url => 'http://hessian.service.com/serviceName',
38             version => 2, # hessian protocol version
39             );
40             my($stat,$res) = $foo->call('add',2,4);
41             if($stat == 0){ # success
42             print "2 + 4 = $res";
43             }else{
44             print "error: $Hessian::Tiny::Client::Error";
45             }
46              
47             =head1 DESCRIPTION
48              
49             Hessian is a compact binary protocol for web communication in form of client/server RPC.
50              
51             This module allows you to write Hessian clients in Perl.
52              
53             This module supports Hessian Protocol 1.0 and 2.0
54              
55             Perl 5.6.0 or later is required to install this modle.
56              
57             =head1 SUBROUTINES/METHODS
58              
59             =head2 new
60              
61             my $foo = Hessian::Tiny::Client->new(
62             url => 'http://hessian.service.com/serviceName', # mandatory
63             version => 2, # default is 1
64             debug => 1, # add some debugging output (to STDERR)
65             auth => [$http_user,$http_pass], # http basic auth, if needed
66             hessian_flag => 1, # if you need strong typing in return value
67             );
68              
69             =over
70              
71             =item 'url'
72              
73             hessian server url, need to be a valid url, otherwise the constructor will return undef.
74              
75             =item 'version'
76              
77             hessian protocol version, 1 or 2.
78              
79             =item 'debug'
80              
81             for debugging, you probably don't need to set this flag.
82              
83             =item 'auth'
84              
85             if http server requires authentication. (passed on to LWP request)
86              
87             =item 'hessian_flag'
88              
89             default off, that means return value are automatically converted into native perl data;
90             if set to true, you will get Hessian::Type::* object as return.
91              
92             =back
93              
94             =cut
95              
96             sub new {
97 2     2 1 889 my($class,@params) = @_;
98 2         8 my $self = {@params};
99 2         20 my $u = URI->new($self->{url});
100 2 50 33     9772 unless(defined $u and $u->scheme and $u->scheme =~ /^http/){
      33        
101 0         0 $Error = qq[Hessian url not valid: '$$self{url}'];
102 0         0 return;
103             }
104 2   50     247 $self->{version} ||= 1; #default v1.0
105 2         25 return bless $self, $class;
106             }
107              
108              
109             =head2 call
110              
111             # for convinience, simple types can be passed directly
112             ($stat,$res) = $foo->call('addInt',1,2);
113              
114             # or use Hessian::Type::* for precise typing
115             ($stat,$res) = $foo->call('method1',
116             Hessian::Type::Date( Math::BigInt->new( $milli_sec ) ),
117             Hessian::Type::Double( 3.14 ),
118             Hessian::Type::List( length=>2,
119             data=>[
120             Hessian::Type::String->new('unicode_stream'),
121             Hessian::Type::Binary->new('bytes')
122             ] );
123             Hessian::Type::Map( type=>'Car',
124             data=>{
125             'Make' => 'Toto',
126             'Modle' => 'XYZ'
127             } );
128            
129             ); # end call
130              
131             if($stat == 0){
132             # success
133              
134             }elsif($stat == 1){
135             # Hessian Fault
136             print "Exception: $res->{code}, $res->{message}";
137             }else{
138             # communication failure
139             print "error: $res";
140             }
141              
142             =over
143              
144             =item return values:
145              
146             B<$stat>: 0 for success, 1 for Hessian level Fault, 2 for other errors such as http communication error or parsing anomaly;
147              
148             B<$res>: will hold the hessian call result if call was successful, or will hold error (Hessian::Fault or string) in case of unsuccessful call;
149              
150             normally Hessian types are converted to perl data directly, if you want strong typing in return value, you can set (hessian_flag => 1) in the constructor call new().
151              
152             =cut
153              
154             sub call {
155 183     183 1 450839 my($self,$method_name,@hessian_params) = @_;
156              
157 183         807 $Error = ''; # reset, probably not needed
158             # open fh to write call
159 183         1180 my $call_fh = File::Temp::tempfile();
160 183 50       327351 return 2, $self->_elog("call, open temp call file failed $!") unless defined $call_fh;
161              
162             # write call to fh
163 183 50       515 eval{
164 183         1336 my $wtr = Hessian::Tiny::Type::_make_writer($call_fh);
165 183 50 33     2793 if( $self->{version} and $self->{version} == 2 ){
166 0         0 Hessian::Tiny::ConvertorV2::write_call($wtr,$method_name,@hessian_params);
167             }else{
168 183         1163 Hessian::Tiny::ConvertorV1::write_call($wtr,$method_name,@hessian_params);
169             }
170 183         681 1;
171             }or return 2, $self->_elog("write_call: $@");
172              
173             # write call successful, rewind & read
174 183         11940 $call_fh->flush();
175 183         1603 seek $call_fh,0,0;
176              
177             # make LWP client
178 183         2471 my $ua = LWP::UserAgent->new;
179 183         71519 $ua->agent("Perl Hessian::Tiny::Client $$self{version}");
180 183         11105 my $header = HTTP::Headers->new();
181              
182 183 0 33     3327 if('ARRAY' eq ref $self->{auth} and
      33        
183             length $self->{auth}->[0] > 0 and
184             length $self->{auth}->[1] > 0
185             ){
186 0         0 $header->authorization;
187 0         0 $header->authorization_basic($self->{auth}->[0],$self->{auth}->[1]);
188             }
189 183         504 my $buf = '';
190 183         1673 binmode $call_fh,':bytes';
191             my $http_request = HTTP::Request->new(POST => $self->{url}, $header,sub{
192 374     374   17589416 read $call_fh,$buf,255; $buf
  374         1882  
193 183         3000 });
194              
195             # send http request
196 183         62958 my $reply_fh = File::Temp::tempfile();
197 183 50       212361 return 2, $self->_elog("call, open temp reply file failed $!") unless defined $reply_fh;
198 183         6067 binmode $reply_fh,':bytes';
199             my $http_response = $ua->request($http_request, sub{
200 209     209   17139910 my($chunk,$res,$lwp) = @_; print $reply_fh $chunk;
  209         3158  
201 183         3244 });
202 183         144775 $call_fh->close;
203              
204 183 50       305603 unless($http_response->is_success){ # http level failure
205 0         0 $reply_fh->close;
206 0         0 return 2, $self->_elog('Hessian http response unsuccessful: ',
207             $http_response->status_line, $http_response->error_as_HTML)
208             ;
209             }
210              
211 183         4889 my($st,$re);
212 183         23441 $reply_fh->flush();
213 183         2052 seek $reply_fh,0,0;
214 183 50       6319 eval{
215 183         2458 ($st,$re) = _read_reply( Hessian::Tiny::Type::_make_reader($reply_fh),$self->{hessian_flag});
216 183         1875 1;
217             } or return 2, $self->_elog("Hessian parse reply: $@");
218 183 100 100     1301 $self->_elog("Fault: $re->{code}; $re->{message}") if $st && 'Hessian::Type::Fault' eq ref $re;
219 183 100       677 $self->_elog($re) if $st == 2;
220 183         166664 return $st,$re;
221             }
222              
223 3 50   3   33 sub _elog { my $self=shift;$Error=join'',@_;print STDERR @_,"\n" if $self->{debug}; join '',@_ }
  3         15  
  3         15  
  3         7  
224             sub _read_reply {
225 183     183   772 my($reader,$hessian_flag) = @_;
226 183         804 my $buf = $reader->(3);
227 183         347 my($or,$m,$obj);
228 183 50       2394 if($buf =~ /^(f|r\x01\x00)/){ # 1.0 reply
    0          
229 183         1784 $or = Hessian::Tiny::ConvertorV1::_make_object_reader($hessian_flag);
230 183 100       453 eval{
231 183 50       971 if($buf =~ /^f/){ # 2.0 compatible mode return fault directly
232 0         0 $reader->(-3); # rewind
233 0         0 $obj = $or->($reader,0);
234 0         0 bless $obj,'Hessian::Type::Fault';
235             }else{ # pure 1.0 reply
236 183         300 do{$obj = $or->($reader)}
  183         717  
237             while('Hessian::Type::Header' eq ref $obj); # discard headers
238             }
239 181         3066 1;
240             } or return 2, $@;
241 181 100       1728 return ('Hessian::Type::Fault' eq ref $obj ? 1 : 0), $obj;
242             }elsif($buf =~ /^H\x02\x00/){ # 2.0 reply
243 0           $m = $reader->(1);
244 0           $or = Hessian::Tiny::ConvertorV2::_make_object_reader($hessian_flag);
245 0 0         eval{
246 0 0         if($m eq 'R'){
    0          
247 0           $obj = $or->($reader);
248             }elsif($m eq 'F'){
249 0           $obj = $or->($reader,0);
250 0           bless $obj,'Hessian::Type::Fault';
251             }else{ # others not implemented
252 0 0         die "response is neither H2 Reply nor H2 Fault: $m" unless $m =~ /^[RF]/;
253             }
254 0           1;
255             } or return 2, $@;
256 0 0         return ('Hessian::Type::Fault' eq ref $obj ? 1 : 0), $obj;
257             }else{ # anomaly
258 0           return 2,"_read_reply: unexpected beginning($buf)";
259             }
260             }
261              
262             =back
263              
264             =head1 HESSIAN DATA TYPES
265              
266             =head2 Null
267              
268             $foo->call('argNull', Hessian::Type::Null->new() );
269              
270             As return value, by default, you will get undef;
271             when 'hessian_flag' is set to true, you will get Hessian::Type::Null.
272              
273             =head2 True/False
274              
275             $foo->call('argTrue', Hessian::Type::True->new() );
276             $foo->call('argFalse', Hessian::Type::False->new() );
277              
278             As return value, by default, you will get 1 (true) or undef (false);
279             when 'hessian_flag' is set to true, you will get Hessian::Type::True
280             or Hessian::Type::False as return value.
281              
282             =head2 Integer
283              
284             $foo->call('argInt', 250 );
285              
286             No extra typing for Integer type.
287             Note, if the number passed in falls outside the range of signed 32-bit integer,
288             it will be passed as a Long type parameter (64-bit) instead.
289              
290             =head2 Long
291              
292             $foo->call('argLong', Math::BigInt->new(100000) ); # core module
293             $foo->call('argLong', Hessian::Type::Long->new('100000') ); # same as above
294              
295             As return value, by default, you will get string representation of the number;
296             when 'hessian_flag' is set to true, you will get Math::BigInt.
297              
298             =head2 Double
299              
300             $foo->call('argDouble', -2.50 ); # pass directly, if looks like floating point number
301             $foo->call('argDouble', Hessian::Type::Double(-2.50) ); # equivalent
302              
303             As return value, by default, you will get the number directly;
304             when 'hessian_flag' is set to true, you will get Hessian::Type::Double.
305             Note, floating point numbers may appear slightly inaccurate, due to the binary nature of machines (not the fault of protocol itself, or Perl even).
306              
307             =head2 Date
308              
309             $foo->call('argDate', Hessian::Type::Date->new($milli_sec) );
310             $foo->call('argDate', DateTime->now() ); # if you have this module installed
311              
312             As return value, by default, you will get epoch seconds;
313             when 'hessian_flag' is set to true, you will get Hessian::Type::Date (milli sec inside).
314              
315             =head2 Binary/String
316              
317             $foo->call('argBinary', Hessian::Type::Binary->new("hello world\n") );
318             $foo->call('argString', Hessian::Type::String->new("hello world\n") );
319             $foo->call('argString', Unicode::String->new("hello world\n") );
320              
321             As return value, by default, you will get the perl string;
322             when 'hessian_flag' is set to true, you will get Hessian::Type::Binary or
323             Hessian::Type::String object. (Binary means byte stream, while String is UTF-8)
324              
325             =head2 XML
326              
327             $foo->call('argXML', Hessian::Type::XML->new( $xml_string ) );
328              
329             As return value, by default, you will get xml string;
330             when 'hessian_flag' is set to true, you will get Hessian::Type::XML.
331             Note, XML type is removed from Hessian 2.0 spec.
332              
333             =head2 List
334              
335             $foo->call('argList', [1,2,3] ); # untyped fixed length list
336             $foo->call('argList', Hessian::Type::List->new([1,2,3]); # same as above
337             $foo->call('argList', Hessian::Type::List->new(length=>3,data=>[1,2,3],type=>'Triplet');
338              
339             As return value, by default, you will get array ref;
340             when 'hessian_flag' is set to true, you will get Hessian::Type::List.
341              
342             =head2 Map
343              
344             $foo->call('argMap', {a=>1,b=>2,c=>3} ); # untyped map
345             $foo->call('argMap', Hessian::Type::Map->new({a=>1,b=>2,c=>3} ); # same as above
346             $foo->call('argMap', Hessian::Type::Map->new(type=>'HashTable',data=>{a=>1,b=>2,c=>3} ); # typed
347              
348             As return value, by default, you will get hash ref (Tie::RefHash is used to allow non-string keys);
349             when 'hessian_flag' is set to true, you will get Hessian::Type::Map.
350              
351             =head2 Object
352              
353             my $x = Hessian::Type::Object->new(
354             type => 'my.package.LinkedList',
355             data => {_value => 1, _rest => undef}
356             );
357             my $y = Hessian::Type::Object->new(
358             type => 'my.package.LinkedList',
359             data => {_value => 2, _rest => $x}
360             );
361             $foo->call('argObject',$y);
362              
363             As return value, by default, you will get hash_ref (Tie::RefHash is used to allow non-string keys);
364             when 'hessian_flag' is set to true, you will get Hessian::Type::Object.
365             Note, Object is essentially a typed Map.
366              
367             =head1 AUTHOR
368              
369             Ling Du, C<< >>
370              
371             =head1 BUGS
372              
373             Please report any bugs or feature requests to C, or through
374             the web interface at L. I will be notified, and then you'll
375             automatically be notified of progress on your bug as I make changes.
376              
377             =head1 TODO
378              
379             Hessian::Tiny::Server, not sure if anyone will need to use the server part, except for testing maybe.
380              
381              
382             =head1 SUPPORT
383              
384             You can find documentation for this module with the perldoc command.
385              
386             perldoc Hessian::Tiny::Client
387              
388              
389             For information on the wonderful protocol itself, take a look at:
390             http://hessian.caucho.com/
391              
392             =over 4
393              
394             =item * RT: CPAN's request tracker
395              
396             L
397              
398             =item * AnnoCPAN: Annotated CPAN documentation
399              
400             L
401              
402             =item * CPAN Ratings
403              
404             L
405              
406             =item * Search CPAN
407              
408             L
409              
410             =back
411              
412              
413             =head1 ACKNOWLEDGEMENTS
414              
415             Algo LLC.
416              
417             =head1 LICENSE AND COPYRIGHT
418              
419             Copyright 2010 Ling Du.
420              
421             This program is free software; you can redistribute it and/or modify it
422             under the terms of either: the GNU General Public License as published
423             by the Free Software Foundation; or the Artistic License.
424              
425             See http://dev.perl.org/licenses/ for more information.
426              
427              
428             =cut
429              
430             1; # End of Hessian::Tiny::Client