File Coverage

blib/lib/Mojo/IOLoop/Client.pm
Criterion Covered Total %
statement 96 130 73.8
branch 24 52 46.1
condition 13 25 52.0
subroutine 26 30 86.6
pod 3 3 100.0
total 162 240 67.5


line stmt bran cond sub pod time code
1             package Mojo::IOLoop::Client;
2 64     64   68376 use Mojo::Base 'Mojo::EventEmitter';
  64         958  
  64         422  
3              
4 64     64   15085 use Errno qw(EINPROGRESS);
  64         40185  
  64         7192  
5 64     64   38560 use IO::Socket::IP;
  64         1348083  
  64         355  
6 64     64   33618 use IO::Socket::UNIX;
  64         180  
  64         684  
7 64     64   102121 use Mojo::IOLoop;
  64         173  
  64         796  
8 64     64   28258 use Mojo::IOLoop::TLS;
  64         275  
  64         788  
9 64     64   488 use Scalar::Util qw(weaken);
  64         168  
  64         3880  
10 64     64   543 use Socket qw(IPPROTO_TCP SOCK_STREAM TCP_NODELAY);
  64         189  
  64         8252  
11              
12             # Non-blocking name resolution requires Net::DNS::Native
13 64 100   64   538 use constant NNR => $ENV{MOJO_NO_NNR} ? 0 : !!eval { require Net::DNS::Native; Net::DNS::Native->VERSION('0.15'); 1 };
  64         240  
  64         513  
  63         19624  
  0         0  
  0         0  
14             my $NDN;
15              
16             # SOCKS support requires IO::Socket::Socks
17             use constant SOCKS => $ENV{MOJO_NO_SOCKS}
18             ? 0
19 64 100   64   519 : !!eval { require IO::Socket::Socks; IO::Socket::Socks->VERSION('0.64'); 1 };
  64         218  
  64         514  
  63         15824  
  0         0  
  0         0  
20 64     64   442 use constant READ => SOCKS ? IO::Socket::Socks::SOCKS_WANT_READ() : 0;
  64         231  
  64         4523  
21 64     64   486 use constant WRITE => SOCKS ? IO::Socket::Socks::SOCKS_WANT_WRITE() : 0;
  64         246  
  64         132459  
22              
23             has reactor => sub { Mojo::IOLoop->singleton->reactor }, weak => 1;
24              
25 198     198   745 sub DESTROY { shift->_cleanup }
26              
27 2     2 1 10 sub can_nnr {NNR}
28 2     2 1 16 sub can_socks {SOCKS}
29              
30             sub connect {
31 197 100   197 1 1533 my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});
32              
33             # Timeout
34 197         873 weaken $self;
35 197         866 my $reactor = $self->reactor;
36 197   100 0   1720 $self->{timer} = $reactor->timer($args->{timeout} || 10, sub { $self->emit(error => 'Connect timeout') });
  0         0  
37              
38             # Blocking name resolution
39 197   66     1825 $_ && s/[[\]]//g for @$args{qw(address socks_address)};
40 197   66     1401 my $address = $args->{socks_address} || ($args->{address} ||= '127.0.0.1');
41 197 100   197   1218 return $reactor->next_tick(sub { $self && $self->_connect($args) }) if !NNR || $args->{handle} || $args->{path};
  197         1506  
42              
43             # Non-blocking name resolution
44 0   0     0 $NDN //= Net::DNS::Native->new(pool => 5, extra_thread => 1);
45             my $handle = $self->{dns}
46 0         0 = $NDN->getaddrinfo($address, _port($args), {protocol => IPPROTO_TCP, socktype => SOCK_STREAM});
47             $reactor->io(
48             $handle => sub {
49 0     0   0 my $reactor = shift;
50              
51 0         0 $reactor->remove($self->{dns});
52 0         0 my ($err, @res) = $NDN->get_result(delete $self->{dns});
53 0 0       0 return $self->emit(error => "Can't resolve: $err") if $err;
54              
55 0         0 $args->{addr_info} = \@res;
56 0         0 $self->_connect($args);
57             }
58 0         0 )->watch($handle, 1, 0);
59             }
60              
61             sub _cleanup {
62 390     390   719 my $self = shift;
63 390 0 33     1029 $NDN->timedout($self->{dns}) if $NDN && $self->{dns};
64 390 50       1136 return $self unless my $reactor = $self->reactor;
65 390   66     2673 $self->{$_} && $reactor->remove(delete $self->{$_}) for qw(dns timer handle);
66 390         4924 return $self;
67             }
68              
69             sub _connect {
70 196     196   603 my ($self, $args) = @_;
71              
72 196         456 my $path = $args->{path};
73 196         590 my $handle = $self->{handle} = $args->{handle};
74              
75 196 50       641 unless ($handle) {
76 196 50       610 my $class = $path ? 'IO::Socket::UNIX' : 'IO::Socket::IP';
77 196         659 my %options = (Blocking => 0);
78              
79             # UNIX domain socket
80 196 50       566 if ($path) { $options{Peer} = $path }
  0         0  
81              
82             # IP socket
83             else {
84 196 50       709 if (my $info = $args->{addr_info}) { $options{PeerAddrInfo} = $info }
  0         0  
85             else {
86 196   66     1220 $options{PeerAddr} = $args->{socks_address} || $args->{address};
87 196         721 $options{PeerPort} = _port($args);
88             }
89 196 100       742 @options{keys %{$args->{socket_options}}} = values %{$args->{socket_options}} if $args->{socket_options};
  180         595  
  180         560  
90             }
91              
92 196 50       1844 return $self->emit(error => "Can't connect: $@") unless $self->{handle} = $handle = $class->new(%options);
93             }
94 196         119975 $handle->blocking(0);
95              
96 196 50       4208 $path ? $self->_try_socks($args) : $self->_wait('_ready', $handle, $args);
97             }
98              
99 196 0 66 196   1424 sub _port { $_[0]{socks_port} || $_[0]{port} || ($_[0]{tls} ? 443 : 80) }
    50          
100              
101             sub _ready {
102 195     195   652 my ($self, $args) = @_;
103              
104             # Socket changes in between attempts and needs to be re-added for epoll/kqueue
105 195         492 my $handle = $self->{handle};
106 195 50       745 unless ($handle->connect) {
107 0 0       0 return $self->emit(error => $!) unless $! == EINPROGRESS;
108 0         0 $self->reactor->remove($handle);
109 0         0 return $self->_wait('_ready', $handle, $args);
110             }
111              
112 195 50 0     14095 return $self->emit(error => $! || 'Not connected') unless $handle->connected;
113              
114             # Disable Nagle's algorithm
115 195         6378 setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
116              
117 195         1188 $self->_try_socks($args);
118             }
119              
120             sub _socks {
121 0     0   0 my ($self, $args) = @_;
122              
123             # Connected
124 0         0 my $handle = $self->{handle};
125 0 0       0 return $self->_try_tls($args) if $handle->ready;
126              
127             # Switch between reading and writing
128 0         0 my $err = $IO::Socket::Socks::SOCKS_ERROR;
129 0 0       0 if ($err == READ) { $self->reactor->watch($handle, 1, 0) }
  0 0       0  
130 0         0 elsif ($err == WRITE) { $self->reactor->watch($handle, 1, 1) }
131 0         0 else { $self->emit(error => $err) }
132             }
133              
134             sub _try_socks {
135 195     195   821 my ($self, $args) = @_;
136              
137 195         530 my $handle = $self->{handle};
138 195 100       1099 return $self->_try_tls($args) unless $args->{socks_address};
139 1         17 return $self->emit(error => 'IO::Socket::Socks 0.64+ required for SOCKS support') unless SOCKS;
140              
141 0         0 my %options = (ConnectAddr => $args->{address}, ConnectPort => $args->{port});
142 0 0       0 @options{qw(AuthType Username Password)} = ('userpass', @$args{qw(socks_user socks_pass)}) if $args->{socks_user};
143 0         0 my $reactor = $self->reactor;
144 0         0 $reactor->remove($handle);
145 0 0       0 return $self->emit(error => 'SOCKS upgrade failed') unless IO::Socket::Socks->start_SOCKS($handle, %options);
146              
147 0         0 $self->_wait('_socks', $handle, $args);
148             }
149              
150             sub _try_tls {
151 194     194   479 my ($self, $args) = @_;
152              
153 194         445 my $handle = $self->{handle};
154 194 100       1136 return $self->_cleanup->emit(connect => $handle) unless $args->{tls};
155 2         8 my $reactor = $self->reactor;
156 2         11 $reactor->remove($handle);
157              
158             # Start TLS handshake
159 2         10 weaken $self;
160 2         17 my $tls = Mojo::IOLoop::TLS->new($handle)->reactor($self->reactor);
161 2     0   20 $tls->on(upgrade => sub { $self->_cleanup->emit(connect => pop) });
  0         0  
162 2     2   12 $tls->on(error => sub { $self->emit(error => pop) });
  2         14  
163 2         15 $tls->negotiate(%$args);
164             }
165              
166             sub _wait {
167 196     196   965 my ($self, $next, $handle, $args) = @_;
168 196         904 weaken $self;
169 196     195   849 $self->reactor->io($handle => sub { $self->$next($args) })->watch($handle, 0, 1);
  195         1364  
170             }
171              
172             1;
173              
174             =encoding utf8
175              
176             =head1 NAME
177              
178             Mojo::IOLoop::Client - Non-blocking TCP/IP and UNIX domain socket client
179              
180             =head1 SYNOPSIS
181              
182             use Mojo::IOLoop::Client;
183              
184             # Create socket connection
185             my $client = Mojo::IOLoop::Client->new;
186             $client->on(connect => sub ($client, $handle) {...});
187             $client->on(error => sub ($client, $err) {...});
188             $client->connect(address => 'example.com', port => 80);
189              
190             # Start reactor if necessary
191             $client->reactor->start unless $client->reactor->is_running;
192              
193             =head1 DESCRIPTION
194              
195             L opens TCP/IP and UNIX domain socket connections for L.
196              
197             =head1 EVENTS
198              
199             L inherits all events from L and can emit the following new ones.
200              
201             =head2 connect
202              
203             $client->on(connect => sub ($client, $handle) {...});
204              
205             Emitted once the connection is established.
206              
207             =head2 error
208              
209             $client->on(error => sub ($client, $err) {...});
210              
211             Emitted if an error occurs on the connection, fatal if unhandled.
212              
213             =head1 ATTRIBUTES
214              
215             L implements the following attributes.
216              
217             =head2 reactor
218              
219             my $reactor = $client->reactor;
220             $client = $client->reactor(Mojo::Reactor::Poll->new);
221              
222             Low-level event reactor, defaults to the C attribute value of the global L singleton. Note that
223             this attribute is weakened.
224              
225             =head1 METHODS
226              
227             L inherits all methods from L and implements the following new ones.
228              
229             =head2 can_nnr
230              
231             my $bool = Mojo::IOLoop::Client->can_nnr;
232              
233             True if L 0.15+ is installed and non-blocking name resolution support enabled.
234              
235             =head2 can_socks
236              
237             my $bool = Mojo::IOLoop::Client->can_socks;
238              
239             True if L 0.64+ is installed and SOCKS5 support enabled.
240              
241             =head2 connect
242              
243             $client->connect(address => '127.0.0.1', port => 3000);
244             $client->connect({address => '127.0.0.1', port => 3000});
245              
246             Open a socket connection to a remote host. Note that non-blocking name resolution depends on L
247             (0.15+), SOCKS5 support on L (0.64), and TLS support on L (2.009+).
248              
249             These options are currently available:
250              
251             =over 2
252              
253             =item address
254              
255             address => 'mojolicious.org'
256              
257             Address or host name of the peer to connect to, defaults to C<127.0.0.1>.
258              
259             =item handle
260              
261             handle => $handle
262              
263             Use an already prepared L object.
264              
265             =item path
266              
267             path => '/tmp/myapp.sock'
268              
269             Path of UNIX domain socket to connect to.
270              
271             =item port
272              
273             port => 80
274              
275             Port to connect to, defaults to C<80> or C<443> with C option.
276              
277             =item socket_options
278              
279             socket_options => {LocalAddr => '127.0.0.1'}
280              
281             Additional options for L when opening new connections.
282              
283             =item socks_address
284              
285             socks_address => '127.0.0.1'
286              
287             Address or host name of SOCKS5 proxy server to use for connection.
288              
289             =item socks_pass
290              
291             socks_pass => 'secr3t'
292              
293             Password to use for SOCKS5 authentication.
294              
295             =item socks_port
296              
297             socks_port => 9050
298              
299             Port of SOCKS5 proxy server to use for connection.
300              
301             =item socks_user
302              
303             socks_user => 'sri'
304              
305             Username to use for SOCKS5 authentication.
306              
307             =item timeout
308              
309             timeout => 15
310              
311             Maximum amount of time in seconds establishing connection may take before getting canceled, defaults to C<10>.
312              
313             =item tls
314              
315             tls => 1
316              
317             Enable TLS.
318              
319             =item tls_ca
320              
321             tls_ca => '/etc/tls/ca.crt'
322              
323             Path to TLS certificate authority file.
324              
325             =item tls_cert
326              
327             tls_cert => '/etc/tls/client.crt'
328              
329             Path to the TLS certificate file.
330              
331             =item tls_key
332              
333             tls_key => '/etc/tls/client.key'
334              
335             Path to the TLS key file.
336              
337             =item tls_options
338              
339             tls_options => {SSL_alpn_protocols => ['foo', 'bar'], SSL_verify_mode => 0x00}
340              
341             Additional options for L.
342              
343             =back
344              
345             =head1 SEE ALSO
346              
347             L, L, L.
348              
349             =cut