File Coverage

blib/lib/AnyEvent/WebSocket/Client.pm
Criterion Covered Total %
statement 81 100 81.0
branch 19 32 59.3
condition 6 9 66.6
subroutine 16 16 100.0
pod 1 1 100.0
total 123 158 77.8


line stmt bran cond sub pod time code
1             package AnyEvent::WebSocket::Client;
2              
3 9     9   3549065 use strict;
  9         63  
  9         219  
4 9     9   41 use warnings;
  9         13  
  9         179  
5 9     9   2090 use Moo;
  9         45625  
  9         46  
6 9     9   10886 use AE;
  9         5719  
  9         227  
7 9     9   48 use AnyEvent;
  9         17  
  9         195  
8 9     9   2927 use AnyEvent::Handle;
  9         71767  
  9         243  
9 9     9   2992 use AnyEvent::Socket ();
  9         70386  
  9         249  
10 9     9   3633 use AnyEvent::Connector;
  9         37305  
  9         296  
11 9     9   1932 use Protocol::WebSocket::Request;
  9         35207  
  9         241  
12 9     9   3236 use Protocol::WebSocket::Handshake::Client;
  9         669541  
  9         257  
13 9     9   3605 use AnyEvent::WebSocket::Connection;
  9         31  
  9         339  
14 9     9   61 use PerlX::Maybe qw( maybe provided );
  9         21  
  9         40  
15              
16             # ABSTRACT: WebSocket client for AnyEvent
17             our $VERSION = '0.53'; # VERSION
18              
19              
20             has timeout => (
21             is => 'ro',
22             default => sub { 30 },
23             );
24              
25              
26             has ssl_no_verify => (
27             is => 'ro',
28             );
29              
30              
31             has ssl_ca_file => (
32             is => 'ro',
33             );
34              
35              
36             has protocol_version => (
37             is => 'ro',
38             );
39              
40              
41             has subprotocol => (
42             is => 'ro',
43             coerce => sub { ref $_[0] ? $_[0] : [$_[0]] },
44             );
45              
46              
47             has http_headers => (
48             is => 'ro',
49             coerce => sub {
50             ref $_[0] eq 'ARRAY' ? $_[0] : do {
51             my $h = shift;
52             [
53             map {
54             my($k,$v) = ($_, $h->{$_});
55             $v = [$v] unless ref $v;
56             map { $k => $_ } @$v;
57             # sorted to make testing easier.
58             # may be removed in the future
59             # so do not depend on it.
60             } sort keys %$h
61             ],
62             };
63             },
64             );
65              
66              
67             has max_payload_size => (
68             is => 'ro',
69             );
70              
71              
72             has max_fragments => (
73             is => 'ro',
74             );
75              
76              
77             has env_proxy => (
78             is => 'ro',
79             default => sub { 0 },
80             );
81              
82              
83              
84             sub connect
85             {
86 22     22 1 3820 my($self, $uri, $host, $port) = @_;
87 22 100       92 unless(ref $uri)
88             {
89 3         20 require URI;
90 3         29 $uri = URI->new($uri);
91             }
92              
93 22         18603 my $done = AE::cv;
94              
95             # TODO: should we also accept http and https URLs?
96             # probably.
97 22 50 66     696 if($uri->scheme ne 'ws' && $uri->scheme ne 'wss')
98             {
99 0         0 $done->croak("URI is not a websocket");
100 0         0 return $done;
101             }
102              
103 22 100       868 $host = $uri->host unless defined $host;
104 22 100       911 $port = $uri->port unless defined $port;
105              
106             $self->_make_tcp_connection($uri->scheme, $host, $port, sub {
107 22     22   2057 my $fh = shift;
108 22 50       72 unless($fh)
109             {
110 0         0 $done->croak("unable to connect");
111 0         0 return;
112             }
113 22         301 my $req = Protocol::WebSocket::Request->new( maybe headers => $self->http_headers );
114 22         995 my $handshake = Protocol::WebSocket::Handshake::Client->new(
115             url => $uri->as_string,
116             maybe version => $self->protocol_version,
117             req => $req,
118             );
119            
120 22         3737 my %subprotocol;
121 22 100       98 if($self->subprotocol)
122             {
123 3         6 %subprotocol = map { $_ => 1 } @{ $self->subprotocol };
  7         19  
  3         10  
124 3         9 $handshake->req->subprotocol(join(',', @{ $self->subprotocol }));
  3         17  
125             }
126            
127             my $hdl = AnyEvent::Handle->new(
128             fh => $fh,
129             provided $uri->secure, tls => 'connect',
130             provided $uri->secure && !$self->ssl_no_verify, peername => $uri->host,
131             provided $uri->secure && !$self->ssl_no_verify, tls_ctx => {
132             verify => 1,
133             verify_peername => "https",
134             maybe ca_file => $self->ssl_ca_file,
135             },
136             on_error => sub {
137 0         0 my ($hdl, $fatal, $msg) = @_;
138 0 0       0 if($fatal)
139 0         0 { $done->croak("connect error: " . $msg) }
140             else
141 0         0 { warn $msg }
142             },
143 22   66     124 );
      66        
144              
145 22         4894 $hdl->push_write($handshake->to_string);
146             $hdl->on_read(sub {
147 22         105676 $handshake->parse($_[0]{rbuf});
148 22 50       6862 if($handshake->error)
    50          
149             {
150 0         0 $done->croak("handshake error: " . $handshake->error);
151 0         0 undef $hdl;
152 0         0 undef $handshake;
153 0         0 undef $done;
154             }
155             elsif($handshake->is_done)
156             {
157 22         380 my $sb;
158 22 100       104 if($self->subprotocol)
159             {
160 3         8 $sb = $handshake->res->subprotocol;
161 3 100       19 if(defined $sb)
162             {
163 2 100       8 unless($subprotocol{$sb})
164             {
165 1         2 $done->croak("subprotocol mismatch, requested: @{[ join ', ', @{ $self->subprotocol } ]}, got: $sb");
  1         3  
  1         10  
166             }
167             }
168             else
169             {
170 1         11 $done->croak("no subprotocol in response");
171             }
172             }
173 22         180 undef $handshake;
174 22         641 $done->send(
175             AnyEvent::WebSocket::Connection->new(
176             handle => $hdl,
177             masked => 1,
178             maybe subprotocol => $sb,
179             maybe max_payload_size => $self->max_payload_size,
180             maybe max_fragments => $self->max_fragments,
181             )
182             );
183 22         237 undef $hdl;
184 22         453 undef $done;
185             }
186 22         5025 });
187 22     22   496 }, sub { $self->timeout });
  22         6512  
188 22         3711 $done;
189             }
190              
191             sub _make_tcp_connection
192             {
193 22     22   411 my $self = shift;
194 22         45 my $scheme = shift;
195 22         59 my ($host, $port) = @_;
196 22 50       120 if(!$self->env_proxy)
197             {
198 22         121 return &AnyEvent::Socket::tcp_connect(@_);
199             }
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__