File Coverage

blib/lib/AnyEvent/WebSocket/Server.pm
Criterion Covered Total %
statement 75 92 81.5
branch 24 36 66.6
condition 5 6 83.3
subroutine 16 17 94.1
pod 3 3 100.0
total 123 154 79.8


line stmt bran cond sub pod time code
1             use strict;
2 9     9   1107588 use warnings;
  9         83  
  9         202  
3 9     9   36 use Carp;
  9         14  
  9         159  
4 9     9   34 use AnyEvent::Handle;
  9         13  
  9         353  
5 9     9   3892 use Protocol::WebSocket::Handshake::Server;
  9         64943  
  9         264  
6 9     9   3363 use Try::Tiny;
  9         1250164  
  9         254  
7 9     9   63 use AnyEvent::WebSocket::Connection;
  9         16  
  9         474  
8 9     9   3305  
  9         72625  
  9         6868  
9             our $VERSION = "0.10";
10              
11             my ($class, %args) = @_;
12             my $validator = $args{validator};
13 54     54 1 794780 if(defined($validator) && ref($validator) ne "CODE") {
14 54         149 croak "validator parameter must be a code-ref";
15 54 50 66     260 }
16 0         0 my $handshake = defined($args{handshake}) ? $args{handshake}
17             : defined($validator) ? sub { my ($req, $res) = @_; return ($res, $validator->($req)); }
18             : sub { $_[1] };
19 9     9   66 if(ref($handshake) ne "CODE") {
  9         46  
20 54 100   168   309 croak "handshake parameter must be a code-ref";
  168 100       1113  
21 54 50       213 }
22 0         0 my $self = bless {
23             handshake => $handshake,
24             map { ($_ => $args{$_}) } qw(ssl_key_file ssl_cert_file max_payload_size),
25             }, $class;
26 54         169 return $self;
  162         473  
27             }
28 54         204  
29             my ($cv) = @_;
30             return sub {
31             my ($handle, $fatal, $message) = @_;
32 212     212   508 if($fatal) {
33             $cv->croak("connection error: $message");
34 4     4   485 }else {
35 4 50       11 warn $message;
36 4         22 }
37             };
38 0         0 }
39              
40 212         1596 my ($self) = @_;
41             if(!defined($self->{ssl_key_file}) && !defined($self->{ssl_cert_file})) {
42             return ();
43             }
44 212     212   452 if(!defined($self->{ssl_cert_file})) {
45 212 100 100     1111 croak "Only ssl_key_file is specified. You need to specify ssl_cert_file, too.";
46 71         264 }
47             return (
48 141 50       481 tls => "accept",
49 0         0 tls_ctx => {
50             cert_file => $self->{ssl_cert_file},
51             defined($self->{ssl_key_file}) ? (key_file => $self->{ssl_key_file}) : ()
52             }
53             );
54             }
55 141 100       1205  
56             my ($self, $cv_connection, $fh, $handshake) = @_;
57             my $handshake_code = $self->{handshake};
58             my $handle = AnyEvent::Handle->new(
59             $self->_handle_args_tls,
60             fh => $fh, on_error => _create_on_error($cv_connection)
61 212     212   609 );
62 212         581 my $read_cb = sub {
63 212         763 ## We don't receive handle object as an argument here. $handle
64             ## is imported in this closure so that $handle becomes
65             ## half-immortal.
66             try {
67             if(!defined($handshake->parse($handle->{rbuf}))) {
68             die "handshake error: " . $handshake->error . "\n";
69             }
70             return if !$handshake->is_done;
71             if($handshake->version ne "draft-ietf-hybi-17") {
72 423 100       22084 die "handshake error: unsupported WebSocket protocol version " . $handshake->version . "\n";
73 4         977 }
74             my ($res, @other_results) = $handshake_code->($handshake->req, $handshake->res);
75 419 100       153948 if(!defined($res)) {
76 204 50       2304 croak "handshake response was undef";
77 0         0 }
78             if(ref($res) eq "Protocol::WebSocket::Response") {
79 204         1852 $res = $res->to_string;
80 195 100       5865 }
81 3         478 $handle->push_write("$res");
82             $cv_connection->send(
83 192 100       671 AnyEvent::WebSocket::Connection->new(handle => $handle, max_payload_size => $self->{max_payload_size}),
84 189         729 @other_results
85             );
86 192         66007 undef $handle;
87             undef $cv_connection;
88 192         26107 }catch {
89             my $e = shift;
90             $cv_connection->croak($e);
91 192         83533 undef $handle;
92 192         726 undef $cv_connection;
93             };
94 16         560 };
95 16         109 $handle->{rbuf} = "";
96 16         12515 $read_cb->(); ## in case the whole request is already consumed
97 16         87 $handle->on_read($read_cb) if defined $handle;
98 423     423   10479567 }
99 212         124197  
100 212         608 my ($self, $fh) = @_;
101 212         537 my $cv_connection = AnyEvent->condvar;
102 212 50       5895 if(!defined($fh)) {
103             $cv_connection->croak("fh parameter is mandatory for establish() method");
104             return $cv_connection;
105             }
106 213     213 1 615966 my $handshake = Protocol::WebSocket::Handshake::Server->new;
107 213         5466 $self->_do_handshake($cv_connection, $fh, $handshake);
108 213 100       2131 return $cv_connection;
109 1         7 }
110 1         45  
111             my ($self, $env, $fh) = @_;
112 212         1401 my $cv_connection = AnyEvent->condvar;
113 212         2322 if(!defined($env)) {
114 212         7396 $cv_connection->croak("psgi_env parameter is mandatory");
115             return $cv_connection;
116             }
117             $fh = $env->{"psgix.io"} if not defined $fh;
118 0     0 1   if(!defined($fh)) {
119 0           $cv_connection->croak("No connection file handle provided. Maybe the PSGI server does not support psgix.io extension.");
120 0 0         return $cv_connection;
121 0           }
122 0           my $handshake = Protocol::WebSocket::Handshake::Server->new_from_psgi($env);
123             $self->_do_handshake($cv_connection, $fh, $handshake);
124 0 0         return $cv_connection;
125 0 0         }
126 0            
127 0           1;
128              
129 0            
130 0           =pod
131 0            
132             =head1 NAME
133              
134             AnyEvent::WebSocket::Server - WebSocket server for AnyEvent
135              
136             =head1 SYNOPSIS
137              
138             use AnyEvent::Socket qw(tcp_server);
139             use AnyEvent::WebSocket::Server;
140            
141             my $server = AnyEvent::WebSocket::Server->new();
142            
143             my $tcp_server;
144             $tcp_server = tcp_server undef, 8080, sub {
145             my ($fh) = @_;
146             $server->establish($fh)->cb(sub {
147             my $connection = eval { shift->recv };
148             if($@) {
149             warn "Invalid connection request: $@\n";
150             close($fh);
151             return;
152             }
153             $connection->on(each_message => sub {
154             my ($connection, $message) = @_;
155             $connection->send($message); ## echo
156             });
157             $connection->on(finish => sub {
158             undef $connection;
159             });
160             });
161             };
162              
163             =head1 DESCRIPTION
164              
165             This class is an implementation of the WebSocket server in an L<AnyEvent> context.
166              
167             =over
168              
169             =item *
170              
171             Currently this module supports WebSocket protocol version 13 only. See L<RFC 6455|https://tools.ietf.org/html/rfc6455> for detail.
172              
173             =back
174              
175              
176             =head1 CLASS METHODS
177              
178             =head2 $server = AnyEvent::WebSocket::Server->new(%args)
179              
180             The constructor.
181              
182             Fields in C<%args> are:
183              
184             =over
185              
186             =item C<handshake> => CODE (optional)
187              
188             A subroutine reference to customize the WebSocket handshake process.
189             You can use this option to validate and preprocess the handshake request and customize the handshake response.
190              
191             For each request, the handshake code is called like
192              
193             ($response, @other_results) = $handshake->($request, $default_response)
194              
195             where C<$request> is a L<Protocol::WebSocket::Request> object,
196             and C<$default_response> is a L<Protocol::WebSocket::Response> object.
197             The C<$handshake> code must return C<$response>. C<@other_results> are optional.
198              
199             The return value C<$response> is the handshake response returned to the client.
200             It must be either a L<Protocol::WebSocket::Response> object,
201             or a string of a valid HTTP response (including the Status-Line, the Headers and the Body).
202              
203             The argument C<$default_response> is a L<Protocol::WebSocket::Response> valid for the given C<$request>.
204             If you don't need to manipulate the response, just return C<$default_response>. That is,
205              
206             handshake => sub { $_[1] }
207              
208             is the minimal valid code for C<handshake>.
209              
210             In addition to C<$response>, you can return C<@other_results> if you want.
211             Those C<@other_results> can be obtained later from the condition variable of C<establish()> method.
212              
213             If you throw an exception from C<$handshake> code, we think you reject the C<$request>.
214             In this case, the condition variable of C<establish()> method croaks.
215              
216              
217             =item C<validator> => CODE (optional)
218              
219             B<< This option is only for backward compatibility. Use C<handshake> option instead. If C<handshake> option is specified, this option is ignored. >>
220              
221             A subroutine reference to validate the incoming WebSocket request.
222             If omitted, it accepts the request.
223              
224             The validator is called like
225              
226             @other_results = $validator->($request)
227              
228             where C<$request> is a L<Protocol::WebSocket::Request> object.
229              
230             If you reject the C<$request>, throw an exception.
231              
232             If you accept the C<$request>, don't throw any exception.
233             The return values of the C<$validator> are sent to the condition variable of C<establish()> method.
234              
235             =item C<ssl_key_file> => FILE_PATH (optional)
236              
237             A string of the filepath to the SSL/TLS private key file in PEM format.
238             If you set this option, you have to set C<ssl_cert_file> option, too.
239              
240             If this option or C<ssl_cert_file> option is set, L<AnyEvent::WebSocket::Server> encrypts the WebSocket streams with SSL/TLS.
241              
242             =item C<ssl_cert_file> => FILE_PATH (optional)
243              
244             A string of the filepath to the SSL/TLS certificate file in PEM format.
245              
246             The file may contain both the certificate and corresponding private key. In that case, C<ssl_key_file> may be omitted.
247              
248             If this option is set, L<AnyEvent::WebSocket::Server> encrypts the WebSocket streams with SSL/TLS.
249              
250             =item C<max_payload_size> => INT (optional)
251              
252             The maximum payload size for received frames. Currently defaults to whatever L<Protocol::WebSocket> defaults to.
253             Note that payload size for sent frames are not limited.
254              
255             =back
256              
257              
258             =head1 OBJECT METHODS
259              
260             =head2 $conn_cv = $server->establish($fh)
261              
262             Establish a WebSocket connection to a client via the given connection filehandle.
263              
264             C<$fh> is a filehandle for a connection socket, which is usually obtained by C<tcp_server()> function in L<AnyEvent::Socket>.
265              
266             Return value C<$conn_cv> is an L<AnyEvent> condition variable.
267              
268             In success, C<< $conn_cv->recv >> returns an L<AnyEvent::WebSocket::Connection> object and C<@other_results> returned by the handshake process.
269             In failure (e.g. the client sent a totally invalid request or your handshake process threw an exception),
270             C<$conn_cv> will croak an error message.
271              
272             ($connection, @other_results) = eval { $conn_cv->recv };
273            
274             ## or in scalar context, it returns $connection only.
275             $connection = eval { $conn_cv->recv };
276            
277             if($@) {
278             my $error = $@;
279             ...
280             return;
281             }
282             do_something_with($connection);
283              
284             You can use C<$connection> to send and receive data through WebSocket. See L<AnyEvent::WebSocket::Connection> for detail.
285              
286             Note that even if C<$conn_cv> croaks, the connection socket C<$fh> remains intact.
287             You can communicate with the client via C<$fh> unless the client has already closed it.
288              
289             =head2 $conn_cv = $server->establish_psgi($psgi_env, [$fh])
290              
291             The same as C<establish()> method except that the request is in the form of L<PSGI> environment.
292              
293             C<$psgi_env> is a L<PSGI> environment object obtained from a L<PSGI> server.
294             C<$fh> is the connection filehandle.
295             If C<$fh> is omitted, C<< $psgi_env->{"psgix.io"} >> is used for the connection (see L<PSGI::Extensions>).
296              
297             =head1 EXAMPLES
298              
299             =head2 handshake option
300              
301             The following server accepts WebSocket URLs such as C<ws://localhost:8080/2013/10>.
302              
303             use AnyEvent::Socket qw(tcp_server);
304             use AnyEvent::WebSocket::Server;
305            
306             my $server = AnyEvent::WebSocket::Server->new(
307             handshake => sub {
308             my ($req, $res) = @_;
309             ## $req is a Protocol::WebSocket::Request
310             ## $res is a Protocol::WebSocket::Response
311            
312             ## validating and parsing request.
313             my $path = $req->resource_name;
314             die "Invalid format" if $path !~ m{^/(\d{4})/(\d{2})};
315            
316             my ($year, $month) = ($1, $2);
317             die "Invalid month" if $month <= 0 || $month > 12;
318            
319             ## setting WebSocket subprotocol in response
320             $res->subprotocol("mytest");
321            
322             return ($res, $year, $month);
323             }
324             );
325            
326             tcp_server undef, 8080, sub {
327             my ($fh) = @_;
328             $server->establish($fh)->cb(sub {
329             my ($conn, $year, $month) = eval { shift->recv };
330             if($@) {
331             my $error = $@;
332             error_response($fh, $error);
333             return;
334             }
335             $conn->send("You are accessing YEAR = $year, MONTH = $month");
336             $conn->on(finish => sub { undef $conn });
337             });
338             };
339              
340             =head1 SEE ALSO
341              
342             =over
343              
344             =item L<AnyEvent::WebSocket::Client>
345              
346             L<AnyEvent>-based WebSocket client implementation.
347              
348             =item L<Net::WebSocket::Server>
349              
350             Minimalistic stand-alone WebSocket server. It uses its own event loop mechanism.
351              
352             =item L<Net::Async::WebSocket>
353              
354             Stand-alone WebSocket server and client implementation using L<IO::Async>
355              
356              
357             =back
358              
359             =head1 AUTHOR
360              
361             Toshio Ito, C<< <toshioito at cpan.org> >>
362              
363             =head1 CONTRIBUTORS
364              
365             mephinet (Philipp Gortan)
366              
367             =head1 REPOSITORY
368              
369             L<https://github.com/debug-ito/AnyEvent-WebSocket-Server>
370              
371             =head1 ACKNOWLEDGEMENTS
372              
373             Graham Ollis (plicease) - author of L<AnyEvent::WebSocket::Client>
374              
375             =head1 LICENSE AND COPYRIGHT
376              
377             Copyright 2013 Toshio Ito.
378              
379             This program is free software; you can redistribute it and/or modify it
380             under the terms of either: the GNU General Public License as published
381             by the Free Software Foundation; or the Artistic License.
382              
383             See http://dev.perl.org/licenses/ for more information.
384