File Coverage

blib/lib/AnyEvent/WebSocket/Client.pm
Criterion Covered Total %
statement 78 98 79.5
branch 19 32 59.3
condition 6 9 66.6
subroutine 15 15 100.0
pod 1 1 100.0
total 119 155 76.7


line stmt bran cond sub pod time code
1             package AnyEvent::WebSocket::Client;
2              
3 9     9   4690672 use strict;
  9         81  
  9         328  
4 9     9   57 use warnings;
  9         19  
  9         221  
5 9     9   2957 use Moo;
  9         58504  
  9         55  
6 9     9   13858 use AE;
  9         7229  
  9         286  
7 9     9   61 use AnyEvent;
  9         21  
  9         233  
8 9     9   3761 use AnyEvent::Handle;
  9         85027  
  9         314  
9 9     9   3547 use AnyEvent::Socket ();
  9         79603  
  9         377  
10 9     9   2964 use Protocol::WebSocket::Request;
  9         41521  
  9         309  
11 9     9   4541 use Protocol::WebSocket::Handshake::Client;
  9         841521  
  9         325  
12 9     9   4847 use AnyEvent::WebSocket::Connection;
  9         35  
  9         384  
13 9     9   81 use PerlX::Maybe qw( maybe provided );
  9         26  
  9         42  
14              
15             # ABSTRACT: WebSocket client for AnyEvent
16             our $VERSION = '0.54'; # VERSION
17              
18              
19             has timeout => (
20             is => 'ro',
21             default => sub { 30 },
22             );
23              
24              
25             has ssl_no_verify => (
26             is => 'ro',
27             );
28              
29              
30             has ssl_ca_file => (
31             is => 'ro',
32             );
33              
34              
35             has protocol_version => (
36             is => 'ro',
37             );
38              
39              
40             has subprotocol => (
41             is => 'ro',
42             coerce => sub { ref $_[0] ? $_[0] : [$_[0]] },
43             );
44              
45              
46             has http_headers => (
47             is => 'ro',
48             coerce => sub {
49             ref $_[0] eq 'ARRAY' ? $_[0] : do {
50             my $h = shift;
51             [
52             map {
53             my($k,$v) = ($_, $h->{$_});
54             $v = [$v] unless ref $v;
55             map { $k => $_ } @$v;
56             # sorted to make testing easier.
57             # may be removed in the future
58             # so do not depend on it.
59             } sort keys %$h
60             ],
61             };
62             },
63             );
64              
65              
66             has max_payload_size => (
67             is => 'ro',
68             );
69              
70              
71             has max_fragments => (
72             is => 'ro',
73             );
74              
75              
76             has env_proxy => (
77             is => 'ro',
78             default => sub { 0 },
79             );
80              
81              
82              
83             sub connect
84             {
85 22     22 1 3741 my($self, $uri, $host, $port) = @_;
86 22 100       84 unless(ref $uri)
87             {
88 3         1912 require URI;
89 3         14721 $uri = URI->new($uri);
90             }
91              
92 22         23207 my $done = AE::cv;
93              
94             # TODO: should we also accept http and https URLs?
95             # probably.
96 22 50 66     778 if($uri->scheme ne 'ws' && $uri->scheme ne 'wss')
97             {
98 0         0 $done->croak("URI is not a websocket");
99 0         0 return $done;
100             }
101              
102 22 100       951 $host = $uri->host unless defined $host;
103 22 100       885 $port = $uri->port unless defined $port;
104              
105             $self->_make_tcp_connection($uri->scheme, $host, $port, sub {
106 22     22   2205 my $fh = shift;
107 22 50       75 unless($fh)
108             {
109 0         0 $done->croak("unable to connect");
110 0         0 return;
111             }
112 22         281 my $req = Protocol::WebSocket::Request->new( maybe headers => $self->http_headers );
113 22         1053 my $handshake = Protocol::WebSocket::Handshake::Client->new(
114             url => $uri->as_string,
115             maybe version => $self->protocol_version,
116             req => $req,
117             );
118              
119 22         4082 my %subprotocol;
120 22 100       109 if($self->subprotocol)
121             {
122 3         6 %subprotocol = map { $_ => 1 } @{ $self->subprotocol };
  7         23  
  3         19  
123 3         13 $handshake->req->subprotocol(join(',', @{ $self->subprotocol }));
  3         21  
124             }
125              
126             my $hdl = AnyEvent::Handle->new(
127             fh => $fh,
128             provided $uri->secure, tls => 'connect',
129             provided $uri->secure && !$self->ssl_no_verify, peername => $uri->host,
130             provided $uri->secure && !$self->ssl_no_verify, tls_ctx => {
131             verify => 1,
132             verify_peername => "https",
133             maybe ca_file => $self->ssl_ca_file,
134             },
135             on_error => sub {
136 0         0 my ($hdl, $fatal, $msg) = @_;
137 0 0       0 if($fatal)
138 0         0 { $done->croak("connect error: " . $msg) }
139             else
140 0         0 { warn $msg }
141             },
142 22   66     119 );
      66        
143              
144 22         5839 $hdl->push_write($handshake->to_string);
145             $hdl->on_read(sub {
146 22         273687 $handshake->parse($_[0]{rbuf});
147 22 50       8487 if($handshake->error)
    50          
148             {
149 0         0 $done->croak("handshake error: " . $handshake->error);
150 0         0 undef $hdl;
151 0         0 undef $handshake;
152 0         0 undef $done;
153             }
154             elsif($handshake->is_done)
155             {
156 22         392 my $sb;
157 22 100       126 if($self->subprotocol)
158             {
159 3         9 $sb = $handshake->res->subprotocol;
160 3 100       20 if(defined $sb)
161             {
162 2 100       8 unless($subprotocol{$sb})
163             {
164 1         2 $done->croak("subprotocol mismatch, requested: @{[ join ', ', @{ $self->subprotocol } ]}, got: $sb");
  1         3  
  1         10  
165             }
166             }
167             else
168             {
169 1         14 $done->croak("no subprotocol in response");
170             }
171             }
172 22         210 undef $handshake;
173 22         651 $done->send(
174             AnyEvent::WebSocket::Connection->new(
175             handle => $hdl,
176             masked => 1,
177             maybe subprotocol => $sb,
178             maybe max_payload_size => $self->max_payload_size,
179             maybe max_fragments => $self->max_fragments,
180             )
181             );
182 22         270 undef $hdl;
183 22         481 undef $done;
184             }
185 22         5497 });
186 22     22   557 }, sub { $self->timeout });
  22         6862  
187 22         3643 $done;
188             }
189              
190             sub _make_tcp_connection
191             {
192 22     22   479 my $self = shift;
193 22         130 my $scheme = shift;
194 22         64 my ($host, $port) = @_;
195 22 50       116 if(!$self->env_proxy)
196             {
197 22         116 return &AnyEvent::Socket::tcp_connect(@_);
198             }
199 0           require AnyEvent::Connector;
200             my @connectors =
201             $scheme eq "ws"
202 0           ? (map { AnyEvent::Connector->new(env_proxy => $_) } qw(ws http))
203             : $scheme eq "wss"
204 0 0         ? (map { AnyEvent::Connector->new(env_proxy => $_) } qw(wss https))
  0 0          
205             : ();
206 0           foreach my $connector (@connectors)
207             {
208 0 0         if(defined($connector->proxy_for($host, $port)))
209             {
210 0           return $connector->tcp_connect(@_);
211             }
212             }
213 0           return &AnyEvent::Socket::tcp_connect(@_);
214             }
215              
216             1;
217              
218             __END__