File Coverage

blib/lib/Net/Async/Tangence/Client.pm
Criterion Covered Total %
statement 84 97 86.6
branch 22 38 57.8
condition 5 14 35.7
subroutine 17 20 85.0
pod 4 7 57.1
total 132 176 75.0


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-2021 -- leonerd@leonerd.org.uk
5              
6             package Net::Async::Tangence::Client 0.16;
7              
8 7     7   340073 use v5.14;
  7         52  
9 7     7   40 use warnings;
  7         14  
  7         342  
10              
11 7     7   40 use base qw( Net::Async::Tangence::Protocol Tangence::Client );
  7         15  
  7         4036  
12              
13 7     7   64073 use Carp;
  7         22  
  7         456  
14              
15 7     7   48 use Future;
  7         16  
  7         167  
16 7     7   38 use Scalar::Util qw( blessed );
  7         18  
  7         414  
17 7     7   48 use Socket ();
  7         14  
  7         143  
18              
19 7     7   4904 use URI;
  7         35081  
  7         8190  
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 7     7 1 65248 my $class = shift;
41 7         32 my %args = @_;
42              
43 7         106 my $self = $class->SUPER::new( %args );
44              
45             # It's possible a handle was passed in the constructor.
46 7 100       1305 $self->tangence_connected( %args ) if defined $self->read_handle;
47              
48 7         534 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 7     7   88 my $self = shift;
74 7         19 my ( $params ) = @_;
75              
76 7         93 $self->identity( delete $params->{identity} );
77              
78 7         138 $self->SUPER::_init( $params );
79              
80 7   100     45 $params->{on_error} ||= "croak";
81             }
82              
83             sub configure
84             {
85 12     12 1 28782 my $self = shift;
86 12         60 my %params = @_;
87              
88 12 100       83 if( my $on_error = delete $params{on_error} ) {
89 7 100       43 if( ref $on_error eq "CODE" ) {
    50          
    0          
90             # OK
91             }
92             elsif( $on_error eq "croak" ) {
93 6     0   35 $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 7         66 $self->on_error( $on_error );
103             }
104              
105 12         513 $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 14     14 1 3563 my $self = shift;
118 14         67 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             =item family => STRING
141              
142             Optional. May be set to C or C to force IPv4 or IPv6 if
143             relevant. Ignored by C and C schemes.
144              
145             =back
146              
147             The following URL schemes are recognised:
148              
149             =over 4
150              
151             =cut
152              
153             sub connect_url
154             {
155 3     3 1 505 my $self = shift;
156 3         11 my ( $url, %args ) = @_;
157              
158 3 50 33     47 my $uri = ( blessed $url && $url->isa( "URI" ) ) ? $url : URI->new( $url );
159              
160 3         19835 my $scheme = $uri->scheme;
161              
162 3 50       314 if( $scheme =~ m/\+/ ) {
163 0 0       0 $scheme =~ s/^circle\+// or croak "Found a + within URL scheme that is not 'circle+'";
164             }
165              
166             # Legacy name
167 3 50       16 $scheme = "sshexec" if $scheme eq "ssh";
168              
169 3         24 my $authority = $uri->authority;
170              
171 3         152 my $path = $uri->path;
172             # Path will start with a leading /; we need to trim that
173 3         48 $path =~ s{^/}{};
174              
175 3         22 my $query = $uri->query;
176 3 100       45 defined $query or $query = "";
177              
178 3         9 my $f;
179              
180 3 100       18 if( $scheme eq "exec" ) {
    100          
    50          
181             # $query will contain args to exec - split them on +
182 1         14 $f = $self->connect_exec( [ $path, split m/\+/, $query ], %args );
183             }
184             elsif( $scheme eq "tcp" ) {
185 1         6 $f = $self->connect_tcp( $authority, %args );
186             }
187             elsif( $scheme eq "unix" ) {
188 1         9 $f = $self->connect_unix( $path, %args );
189             }
190             else {
191 0         0 my $connectorpkg = "Net::Async::Tangence::Client::via::$scheme";
192 0         0 ( my $connectorfile = "$connectorpkg.pm" ) =~ s{::}{/}g;
193 0 0 0     0 if( eval { require $connectorfile } and
  0         0  
194             my $code = $connectorpkg->can( 'connect' ) ) {
195 0         0 $f = $code->( $self, $uri, %args );
196             }
197             else {
198 0         0 croak "Unrecognised URL scheme name '$scheme'";
199             }
200             }
201              
202             return $f->then( sub {
203 3     3   703 my $on_root = $args{on_root};
204              
205 3         13 my $root_f = $self->new_future;
206              
207             $self->tangence_connected( %args,
208             on_root => sub {
209 3         20271 my ( $root ) = @_;
210              
211 3 50       28 $on_root->( $root ) if $on_root;
212 3         61 $root_f->done( $root );
213             },
214 3         2559 );
215              
216 3         957 $root_f;
217 3         25807 });
218             }
219              
220             =item * exec
221              
222             Directly executes the server as a child process. This is largely provided for
223             testing purposes, as the server will only run for this one client; it will
224             exit when the client disconnects.
225              
226             exec:///path/to/command?with+arguments
227              
228             The URL's path should point to the required command, and the query string will
229             be split on C<+> signs and used as the arguments. The authority section of the
230             URL will be ignored, so may be left empty.
231              
232             =cut
233              
234             sub connect_exec
235             {
236 1     1 0 3 my $self = shift;
237 1         7 my ( $command ) = @_;
238              
239 1         33 my $loop = $self->get_loop;
240              
241 1 50       69 pipe( my $myread, my $childwrite ) or croak "Cannot pipe - $!";
242 1 50       37 pipe( my $childread, my $mywrite ) or croak "Cannoe pipe - $!";
243              
244             $loop->spawn_child(
245             command => $command,
246              
247             setup => [
248             stdin => $childread,
249             stdout => $childwrite,
250             ],
251              
252             on_exit => sub {
253 0     0   0 my ( undef, $exitcode, $dollarbang ) = @_;
254 0         0 print STDERR "Child exited unexpectedly (status=$exitcode, \$!=$dollarbang)\n";
255             },
256 1         25 );
257              
258 1         9913 $self->configure(
259             read_handle => $myread,
260             write_handle => $mywrite,
261             );
262              
263 1         552 Future->done;
264             }
265              
266             =item * sshexec
267              
268             A convenient wrapper around the C scheme, to connect to a server running
269             remotely via F.
270              
271             sshexec://host/path/to/command?with+arguments
272              
273             The URL's authority section will give the SSH server (and optionally
274             username), and the path and query sections will be used as for C.
275              
276             (This scheme is also available as C, though this name is now deprecated)
277              
278             =cut
279              
280             =item * tcp
281              
282             Connects to a server via a TCP socket.
283              
284             tcp://host:port/
285              
286             The URL's authority section will be used to give the server's hostname and
287             port number. The other sections of the URL will be ignored.
288              
289             =cut
290              
291             sub connect_tcp
292             {
293 1     1 0 2 my $self = shift;
294 1         3 my ( $authority, %args ) = @_;
295              
296 1         2 my $family;
297 1 50 33     7 $family = Socket::PF_INET() if $args{family} and $args{family} eq "inet4";
298 1 50 33     4 $family = Socket::PF_INET6() if $args{family} and $args{family} eq "inet6";
299              
300 1         5 my ( $host, $port ) = $authority =~ m/^(.*):(.*)$/;
301              
302 1         13 $self->connect(
303             host => $host,
304             service => $port,
305             );
306             }
307              
308             =item * unix
309              
310             Connects to a server via a UNIX local socket.
311              
312             unix:///path/to/socket
313              
314             The URL's path section will give the path to the local socket. The other
315             sections of the URL will be ignored.
316              
317             =cut
318              
319             sub connect_unix
320             {
321 1     1 0 3 my $self = shift;
322 1         4 my ( $path ) = @_;
323              
324 1         43 $self->connect(
325             addr => {
326             family => 'unix',
327             socktype => 'stream',
328             path => $path,
329             },
330             );
331             }
332              
333             =item * sshunix
334              
335             Connects to a server running remotely via a UNIX socket over F.
336              
337             sshunix://host/path/to/socket
338              
339             (This is implemented by running F remotely and sending it a tiny
340             self-contained program that connects STDIN/STDOUT to the given UNIX socket
341             path. It requires that the server has F at least version 5.6 available
342             in the path simply as C)
343              
344             =cut
345              
346             =back
347              
348             =cut
349              
350             =head1 AUTHOR
351              
352             Paul Evans
353              
354             =cut
355              
356             0x55AA;