File Coverage

blib/lib/Net/Async/Tangence/Client.pm
Criterion Covered Total %
statement 40 104 38.4
branch 6 36 16.6
condition 2 2 100.0
subroutine 11 22 50.0
pod 4 9 44.4
total 63 173 36.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2010-2015 -- leonerd@leonerd.org.uk
5              
6             package Net::Async::Tangence::Client;
7              
8 4     4   118102 use strict;
  4         6  
  4         114  
9 4     4   13 use warnings;
  4         5  
  4         104  
10              
11 4     4   14 use base qw( Net::Async::Tangence::Protocol Tangence::Client );
  4         7  
  4         1731  
12              
13             our $VERSION = '0.14';
14              
15 4     4   23789 use Carp;
  4         6  
  4         163  
16              
17 4     4   12 use Future;
  4         13  
  4         111  
18              
19 4     4   1657 use URI::Split qw( uri_split );
  4         7226  
  4         3302  
20              
21             =head1 NAME
22              
23             C - connect to a C server using
24             C
25              
26             =head1 DESCRIPTION
27              
28             This subclass of L connects to a L
29             server, allowing the client program to access exposed objects in the server.
30             It is a concrete implementation of the C mixin.
31              
32             The following documentation concerns this specific implementation of the
33             client; for more general information on the C-specific parts of this
34             class, see instead the documentation for L.
35              
36             =cut
37              
38             sub new
39             {
40 4     4 1 3692 my $class = shift;
41 4         12 my %args = @_;
42              
43 4         35 my $self = $class->SUPER::new( %args );
44              
45             # It's possible a handle was passed in the constructor.
46 4 50       475 $self->tangence_connected( %args ) if defined $self->read_handle;
47              
48 4         270 return $self;
49             }
50              
51             =head1 PARAMETERS
52              
53             The following named parameters may be passed to C or C:
54              
55             =over 8
56              
57             =item identity => STRING
58              
59             The identity string to send to the server.
60              
61             =item on_error => STRING or CODE
62              
63             Default error-handling policy for method calls. If set to either of the
64             strings C or C then a CODE ref will be created that invokes the
65             given function from C; otherwise must be a CODE ref.
66              
67             =back
68              
69             =cut
70              
71             sub _init
72             {
73 4     4   28 my $self = shift;
74 4         11 my ( $params ) = @_;
75              
76 4         29 $self->identity( delete $params->{identity} );
77              
78 4         45 $self->SUPER::_init( $params );
79              
80 4   100     20 $params->{on_error} ||= "croak";
81             }
82              
83             sub configure
84             {
85 6     6 1 10207 my $self = shift;
86 6         16 my %params = @_;
87              
88 6 100       21 if( my $on_error = delete $params{on_error} ) {
89 4 100       21 if( ref $on_error eq "CODE" ) {
    50          
    0          
90             # OK
91             }
92             elsif( $on_error eq "croak" ) {
93 3     0   12 $on_error = sub { croak "Received MSG_ERROR: $_[0]" };
  0         0  
94             }
95             elsif( $on_error eq "carp" ) {
96 0     0   0 $on_error = sub { carp "Received MSG_ERROR: $_[0]" };
  0         0  
97             }
98             else {
99 0         0 croak "Expected 'on_error' to be CODE reference or strings 'croak' or 'carp'";
100             }
101              
102 4         24 $self->on_error( $on_error );
103             }
104              
105 6         42 $self->SUPER::configure( %params );
106             }
107              
108             =head1 METHODS
109              
110             The following methods documented with a trailing call to C<< ->get >> return
111             L instances.
112              
113             =cut
114              
115             sub new_future
116             {
117 4     4 1 901 my $self = shift;
118 4         14 return $self->loop->new_future;
119             }
120              
121             =head2 connect_url
122              
123             $rootobj = $client->connect_url( $url, %args )->get
124              
125             Connects to a C server at the given URL. The returned L will
126             yield the root object proxy once it has been obtained.
127              
128             Takes the following named arguments:
129              
130             =over 8
131              
132             =item on_registry => CODE
133              
134             =item on_root => CODE
135              
136             Invoked once the registry and root object proxies have been obtained from the
137             server. See the documentation the L C
138             method.
139              
140             =back
141              
142             The following URL schemes are recognised:
143              
144             =over 4
145              
146             =cut
147              
148             sub connect_url
149             {
150 0     0 1   my $self = shift;
151 0           my ( $url, %args ) = @_;
152              
153 0           my ( $scheme, $authority, $path, $query, $fragment ) = uri_split( $url );
154              
155 0 0         defined $scheme or croak "Invalid URL '$url'";
156              
157 0 0         if( $scheme =~ m/\+/ ) {
158 0 0         $scheme =~ s/^circle\+// or croak "Found a + within URL scheme that is not 'circle+'";
159             }
160              
161             # Legacy name
162 0 0         $scheme = "sshexec" if $scheme eq "ssh";
163              
164 0           my $f;
165              
166 0 0         if( $scheme eq "exec" ) {
    0          
    0          
    0          
    0          
167             # Path will start with a leading /; we need to trim that
168 0           $path =~ s{^/}{};
169             # $query will contain args to exec - split them on +
170 0           my @argv = split( m/\+/, $query );
171 0           $f = $self->connect_exec( [ $path, @argv ], %args );
172             }
173             elsif( $scheme eq "sshexec" ) {
174             # Path will start with a leading /; we need to trim that
175 0           $path =~ s{^/}{};
176             # $query will contain args to exec - split them on +
177 0           my @argv = split( m/\+/, $query );
178 0           $f = $self->connect_sshexec( $authority, [ $path, @argv ], %args );
179             }
180             elsif( $scheme eq "tcp" ) {
181 0           $f = $self->connect_tcp( $authority, %args );
182             }
183             elsif( $scheme eq "unix" ) {
184             # Path will start with a leading /; we need to trim that
185 0           $path =~ s{^/}{};
186 0           $f = $self->connect_unix( $path, %args );
187             }
188             elsif( $scheme eq "sshunix" ) {
189             # Path will start with a leading /; we need to trim that
190 0           $path =~ s{^/}{};
191 0           $f = $self->connect_sshunix( $authority, $path, %args );
192             }
193             else {
194 0           croak "Unrecognised URL scheme name '$scheme'";
195             }
196              
197             return $f->then( sub {
198 0     0     my $on_root = $args{on_root};
199              
200 0           my $root_f = $self->new_future;
201              
202             $self->tangence_connected( %args,
203             on_root => sub {
204 0           my ( $root ) = @_;
205              
206 0 0         $on_root->( $root ) if $on_root;
207 0           $root_f->done( $root );
208             },
209 0           );
210              
211 0           $root_f;
212 0           });
213             }
214              
215             =item * exec
216              
217             Directly executes the server as a child process. This is largely provided for
218             testing purposes, as the server will only run for this one client; it will
219             exit when the client disconnects.
220              
221             exec:///path/to/command?with+arguments
222              
223             The URL's path should point to the required command, and the query string will
224             be split on C<+> signs and used as the arguments. The authority section of the
225             URL will be ignored, so may be left empty.
226              
227             =cut
228              
229             sub connect_exec
230             {
231 0     0 0   my $self = shift;
232 0           my ( $command, %args ) = @_;
233              
234 0           my $loop = $self->get_loop;
235              
236 0 0         pipe( my $myread, my $childwrite ) or croak "Cannot pipe - $!";
237 0 0         pipe( my $childread, my $mywrite ) or croak "Cannoe pipe - $!";
238              
239             $loop->spawn_child(
240             command => $command,
241              
242             setup => [
243             stdin => $childread,
244             stdout => $childwrite,
245             ],
246              
247             on_exit => sub {
248 0     0     print STDERR "Child exited unexpectedly\n";
249             },
250 0           );
251              
252 0           $self->configure(
253             read_handle => $myread,
254             write_handle => $mywrite,
255             );
256              
257 0           Future->done;
258             }
259              
260             =item * sshexec
261              
262             A convenient wrapper around the C scheme, to connect to a server running
263             remotely via F.
264              
265             sshexec://host/path/to/command?with+arguments
266              
267             The URL's authority section will give the SSH server (and optionally
268             username), and the path and query sections will be used as for C.
269              
270             (This scheme is also available as C, though this name is now deprecated)
271              
272             =cut
273              
274             sub connect_sshexec
275             {
276 0     0 0   my $self = shift;
277 0           my ( $host, $argv, %args ) = @_;
278              
279 0           $self->connect_exec( [ "ssh", $host, @$argv ], %args );
280             }
281              
282             =item * tcp
283              
284             Connects to a server via a TCP socket.
285              
286             tcp://host:port/
287              
288             The URL's authority section will be used to give the server's hostname and
289             port number. The other sections of the URL will be ignored.
290              
291             =cut
292              
293             sub connect_tcp
294             {
295 0     0 0   my $self = shift;
296 0           my ( $authority, %args ) = @_;
297              
298 0           my ( $host, $port ) = $authority =~ m/^(.*):(.*)$/;
299              
300 0           $self->connect(
301             host => $host,
302             service => $port,
303             );
304             }
305              
306             =item * unix
307              
308             Connects to a server via a UNIX local socket.
309              
310             unix:///path/to/socket
311              
312             The URL's path section will give the path to the local socket. The other
313             sections of the URL will be ignored.
314              
315             =cut
316              
317             sub connect_unix
318             {
319 0     0 0   my $self = shift;
320 0           my ( $path, %args ) = @_;
321              
322 0           $self->connect(
323             addr => {
324             family => 'unix',
325             socktype => 'stream',
326             path => $path,
327             },
328             );
329             }
330              
331             =item * sshunix
332              
333             Connects to a server running remotely via a UNIX socket over F.
334              
335             sshunix://host/path/to/socket
336              
337             (This is implemented by running F remotely and sending it a tiny
338             self-contained program that connects STDIN/STDOUT to the given UNIX socket
339             path. It requires that the server has F at least version 5.6 available
340             in the path simply as C)
341              
342             =cut
343              
344             # A tiny program we can run remotely to connect STDIN/STDOUT to a UNIX socket
345             # given as $ARGV[0]
346 4     4   21 use constant _NC_MICRO => <<'EOPERL';
  4         4  
  4         680  
347             use Socket qw( AF_UNIX SOCK_STREAM pack_sockaddr_un );
348             use IO::Handle;
349             socket(my $socket, AF_UNIX, SOCK_STREAM, 0) or die "socket(AF_UNIX): $!\n";
350             connect($socket, pack_sockaddr_un($ARGV[0])) or die "connect $ARGV[0]: $!\n";
351             my $fd = fileno($socket);
352             $socket->blocking(0); $socket->autoflush(1);
353             STDIN->blocking(0); STDOUT->autoflush(1);
354             my $rin = "";
355             vec($rin, 0, 1) = 1;
356             vec($rin, $fd, 1) = 1;
357             print "READY";
358             while(1) {
359             select(my $rout = $rin, undef, undef, undef);
360             if(vec($rout, 0, 1)) {
361             sysread STDIN, my $buffer, 8192 or last;
362             print $socket $buffer;
363             }
364             if(vec($rout, $fd, 1)) {
365             sysread $socket, my $buffer, 8192 or last;
366             print $buffer;
367             }
368             }
369             EOPERL
370              
371             sub connect_sshunix
372             {
373 0     0 0   my $self = shift;
374 0           my ( $host, $path, %args ) = @_;
375              
376             # Tell perl we're going to send it a program on STDIN
377             $self->connect_sshexec( $host, [ 'perl', '-', $path ], %args )
378             ->then( sub {
379 0     0     $self->write( _NC_MICRO . "\n__END__\n" );
380 0           my $f = $self->new_future;
381              
382             $self->configure( on_read => sub {
383 0           my ( $self, $buffref, $eof ) = @_;
384 0 0         return 0 unless $$buffref =~ s/READY//;
385 0           $self->configure( on_read => undef );
386 0           $f->done;
387 0           return 0;
388 0           } );
389              
390 0           return $f;
391 0           });
392             }
393              
394             =back
395              
396             =cut
397              
398             =head1 AUTHOR
399              
400             Paul Evans
401              
402             =cut
403              
404             0x55AA;