File Coverage

blib/lib/Net/Async/WebSocket/Client.pm
Criterion Covered Total %
statement 60 68 88.2
branch 8 16 50.0
condition 3 12 25.0
subroutine 16 17 94.1
pod 3 3 100.0
total 90 116 77.5


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-2024 -- leonerd@leonerd.org.uk
5              
6             package Net::Async::WebSocket::Client 0.14;
7              
8 3     3   568174 use v5.14;
  3         14  
9 3     3   20 use warnings;
  3         8  
  3         222  
10 3     3   23 use base qw( Net::Async::WebSocket::Protocol );
  3         9  
  3         1268  
11              
12             IO::Async::Notifier->VERSION( '0.63' ); # ->adopt_future
13              
14 3     3   20 use Carp;
  3         6  
  3         448  
15              
16             BEGIN {
17 3 50   3   47 if( $^V ge v5.40 ) {
18 3         116 *blessed = \&builtin::blessed;
19             }
20             else {
21 0         0 require Scalar::Util;
22 0         0 *blessed = \&Scalar::Util::blessed;
23             }
24             }
25              
26 3     3   1856 use URI;
  3         16474  
  3         100  
27 3     3   1371 use URI::wss;
  3         26574  
  3         119  
28              
29             BEGIN {
30             # We also need to support ->resource_name, which the CPAN module does not
31             # understand as of 2017-01-01
32 3     3   22 no warnings 'once';
  3         5  
  3         219  
33             *URI::wss::resource_name = sub {
34             shift->path_query
35 3 50   3   97 } unless URI::wss->can( "resource_name" );
  0     0   0  
36             }
37              
38 3     3   1479 use Protocol::WebSocket::Handshake::Client;
  3         56681  
  3         1491  
39              
40             =head1 NAME
41              
42             C - connect to a WebSocket server using
43             C
44              
45             =head1 SYNOPSIS
46              
47             use Future::AsyncAwait;
48              
49             use IO::Async::Loop;
50             use Net::Async::WebSocket::Client;
51              
52             my $client = Net::Async::WebSocket::Client->new(
53             on_text_frame => sub {
54             my ( $self, $frame ) = @_;
55             print $frame;
56             },
57             );
58              
59             my $loop = IO::Async::Loop->new;
60             $loop->add( $client );
61              
62             await $client->connect( url => "ws://$HOST:$PORT/" );
63              
64             await $client->send_text_frame( "Hello, world!\n" );
65              
66             $loop->run;
67              
68             =head1 DESCRIPTION
69              
70             This subclass of L connects to a WebSocket
71             server to establish a WebSocket connection for passing frames.
72              
73             =cut
74              
75             sub new
76             {
77 3     3 1 176296 my $class = shift;
78 3         34 return $class->SUPER::new(
79             masked => 1,
80             @_,
81             );
82             }
83              
84             =head1 METHODS
85              
86             The following methods documented in an C expression return L
87             instances.
88              
89             =cut
90              
91             sub _do_handshake
92             {
93 3     3   7 my $self = shift;
94 3         22 my %params = @_;
95              
96             my $hs = Protocol::WebSocket::Handshake::Client->new(
97             url => $params{url},
98             req => $params{req},
99 3         38 );
100              
101 3         667 $self->debug_printf( "HANDSHAKE start" );
102 3         16 $self->write( $hs->to_string );
103              
104 3         1110 my $f = $self->loop->new_future;
105             $self->SUPER::configure( on_read => sub {
106 2     2   4911 my ( undef, $buffref, $closed ) = @_;
107              
108 2         13 $hs->parse( $$buffref ); # modifies $$buffref
109              
110 2 50       632 if( $hs->is_done ) {
111 2         34 $self->debug_printf( "HANDSHAKE done" );
112 2         36 $self->SUPER::configure( on_read => undef );
113              
114 2         142 $f->done( $self );
115             }
116              
117 2         159 return 0;
118 3         1336 } );
119              
120 3         192 return $f;
121             }
122              
123             =head2 connect
124              
125             await $self->connect( %params );
126              
127             Connect to a WebSocket server. Takes the following named parameters:
128              
129             =over 8
130              
131             =item url => STRING
132              
133             URL to provide to WebSocket handshake. This is also used to infer the host and
134             service name (port number) if not otherwise supplied.
135              
136             =item req => Protocol::WebSocket::Request
137              
138             Optional. If provided, gives the L instance used
139             for performing the handshake.
140              
141             =back
142              
143             The returned L returns the client instance itself, making it useful
144             in chaining constructors.
145              
146             =head2 connect (void)
147              
148             $self->connect( %params );
149              
150             When not returning a C, the following additional parameters provide
151             continuations:
152              
153             =over 8
154              
155             =item on_connected => CODE
156              
157             CODE reference to invoke when the handshaking is complete.
158              
159             =back
160              
161             =cut
162              
163             sub connect
164             {
165 1     1 1 340 my $self = shift;
166 1         6 my %params = @_;
167              
168 1 50       11 if( my $url = $params{url} ) {
169 1 50 33     11 $url = URI->new( $url ) unless blessed $url and $url->isa( "URI" );
170              
171 1   33     1018 $params{host} //= $url->host;
172 1   33     2 $params{service} //= $url->port;
173              
174 1 50       7 if( $url->secure ) {
175 0         0 require IO::Async::SSL;
176 0         0 push @{ $params{extensions} }, qw( SSL );
  0         0  
177 0   0     0 $params{SSL_hostname} //= $url->host;
178             }
179             }
180              
181 1         5 my $on_connected = delete $params{on_connected};
182              
183             my $f = $self->SUPER::connect( %params )->then( sub {
184 1     1   421 my ( $self ) = @_;
185              
186 1         6 $self->_do_handshake( %params );
187 1         10 });
188              
189 1 50       29669 $f->on_done( $on_connected ) if $on_connected;
190              
191 1 50       25 return $f if defined wantarray;
192              
193 0         0 $self->adopt_future( $f );
194             }
195              
196             =head2 connect_handle
197              
198             await $client->connect_handle( $handle, %params );
199              
200             Sets the read and write handles to the IO reference given, then performs the
201             initial handshake using the parameters given. These are as for C.
202              
203             =cut
204              
205             sub connect_handle
206             {
207 2     2 1 6195 my $self = shift;
208 2         10 my ( $handle, %params ) = @_;
209              
210 2         26 $self->set_handle( $handle );
211              
212 2         608 $self->_do_handshake( %params );
213             }
214              
215             =head1 AUTHOR
216              
217             Paul Evans
218              
219             =cut
220              
221             0x55AA;