File Coverage

blib/lib/Net/WebSocket/Streamer.pm
Criterion Covered Total %
statement 37 39 94.8
branch 6 8 75.0
condition 1 3 33.3
subroutine 10 10 100.0
pod 0 3 0.0
total 54 63 85.7


line stmt bran cond sub pod time code
1             package Net::WebSocket::Streamer;
2              
3             =encoding utf-8
4              
5             =head1 NAME
6              
7             Net::WebSocket::Streamer - Send a stream easily over WebSocket
8              
9             =head1 SYNOPSIS
10              
11             Here’s the gist of it:
12              
13             my $streamer = Net::WebSocket::Streamer->new('binary');
14              
15             my $frame = $streamer->create_chunk($buf);
16              
17             my $last_frame = $streamer->create_final($buf);
18              
19             … but a more complete example might be this: streaming a file
20             of arbitrary size in 64-KiB chunks:
21              
22             my $size = -s $rfh;
23              
24             while ( read $rfh, my $buf, 65536 ) {
25             my $frame;
26              
27             if (tell($rfh) == $size) {
28             $frame = $streamer->create_final($buf);
29             }
30             else {
31             $frame = $streamer->create_chunk($buf);
32             }
33              
34             syswrite $wfh, $frame->to_bytes();
35             }
36              
37             You can, of course, create/send an empty final frame for cases where you’re
38             not sure how much data will actually be sent.
39              
40             =head1 EXTENSION SUPPORT
41              
42             To stream custom frame types (or overridden classes), you can subclass
43             this module and define C constants, where C<*> is the
44             frame type, e.g., C, C.
45              
46             =cut
47              
48 1     1   251 use strict;
  1         1  
  1         22  
49 1     1   4 use warnings;
  1         2  
  1         18  
50              
51 1     1   318 use Net::WebSocket::Frame::continuation ();
  1         2  
  1         15  
52 1     1   5 use Net::WebSocket::X ();
  1         1  
  1         24  
53              
54             use constant {
55              
56             #These can be overridden in subclasses.
57 1         326 frame_class_text => 'Net::WebSocket::Frame::text',
58             frame_class_binary => 'Net::WebSocket::Frame::binary',
59              
60             FINISHED_INDICATOR => __PACKAGE__ . '::__ALREADY_SENT_FINAL',
61 1     1   4 };
  1         2  
62              
63             sub new {
64 18     18 0 1481 my ($class, $type) = @_;
65              
66 18         39 my $frame_class = $class->_load_frame_class($type);
67              
68 18         65 return bless { class => $frame_class, pid => $$ }, $class;
69             }
70              
71             sub create_chunk {
72 35     35 0 244 my $self = shift;
73              
74 35         93 my $frame = $self->{'class'}->new(
75             fin => 0,
76             $self->FRAME_MASK_ARGS(),
77             payload_sr => \$_[0],
78             );
79              
80             #The first $frame we create needs to be text/binary, but all
81             #subsequent ones must be continuation.
82 35 100       91 if ($self->{'class'} ne 'Net::WebSocket::Frame::continuation') {
83 18         23 $self->{'class'} = 'Net::WebSocket::Frame::continuation';
84             }
85              
86 35         83 return $frame;
87             }
88              
89             sub create_final {
90 18     18 0 82 my $self = shift;
91              
92 18         40 my $frame = $self->{'class'}->new(
93             fin => 1,
94             $self->FRAME_MASK_ARGS(),
95             payload_sr => \$_[0],
96             );
97              
98 18         38 $self->{'finished'} = 1;
99              
100 18         41 return $frame;
101             }
102              
103             sub _load_frame_class {
104 18     18   27 my ($class, $type) = @_;
105              
106 18         80 my $frame_class = $class->can("frame_class_$type");
107 18 50       39 if (!$frame_class) {
108 0         0 die "Unknown frame type: “$type”!";
109             }
110              
111 18         37 $frame_class = $frame_class->();
112 18 100       65 if (!$frame_class->can('new')) {
113 1         6 Module::Load::load($frame_class);
114             }
115              
116 18         47 return $frame_class;
117             }
118              
119             sub DESTROY {
120 18     18   43 my ($self) = @_;
121              
122 18 50 33     87 if (($self->{'pid'} == $$) && !$self->{'finished'}) {
123 0         0 die Net::WebSocket::X->create('UnfinishedStream', $self);
124             }
125              
126 18         126 return;
127             }
128              
129             1;