File Coverage

blib/lib/Net/WebSocket/Streamer.pm
Criterion Covered Total %
statement 39 42 92.8
branch 6 8 75.0
condition 1 3 33.3
subroutine 11 11 100.0
pod 0 3 0.0
total 57 67 85.0


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 - Stream a WebSocket message easily
8              
9             =head1 SYNOPSIS
10              
11             Here’s the gist of it:
12              
13             #Use the ::Client or ::Server subclass as needed.
14             my $streamer = Net::WebSocket::Streamer::Client->new('binary');
15              
16             my $frame = $streamer->create_chunk($buf);
17              
18             my $last_frame = $streamer->create_final($buf);
19              
20             … but a more complete example might be this: streaming a file
21             of arbitrary size in 64-KiB chunks:
22              
23             my $size = -s $rfh;
24              
25             while ( read $rfh, my $buf, 65536 ) {
26             my $frame;
27              
28             if (tell($rfh) == $size) {
29             $frame = $streamer->create_final($buf);
30             }
31             else {
32             $frame = $streamer->create_chunk($buf);
33             }
34              
35             syswrite $wfh, $frame->to_bytes();
36             }
37              
38             You can, of course, create/send an empty final frame for cases where you’re
39             not sure how much data will actually be sent.
40              
41             Note that the receiving application won’t necessarily have access to the
42             individual message fragments (i.e., frames) that you send. Web browsers,
43             for example, only expose messages, not frames. You may thus be better off
44             sending full messages rather than frames.
45              
46             =head1 EXTENSION SUPPORT
47              
48             To stream custom frame types (or overridden classes), you can pass in
49             a full package name to C rather than merely C or C.
50              
51             =cut
52              
53 1     1   441 use strict;
  1         2  
  1         32  
54 1     1   5 use warnings;
  1         2  
  1         22  
55              
56 1     1   5 use Module::Runtime ();
  1         1  
  1         14  
57              
58 1     1   433 use Net::WebSocket::Frame::continuation ();
  1         2  
  1         21  
59 1     1   6 use Net::WebSocket::X ();
  1         2  
  1         30  
60              
61             use constant {
62              
63             #The old way of doing this. No longer documented,
64             #but still supported.
65 1         391 frame_class_text => 'Net::WebSocket::Frame::text',
66             frame_class_binary => 'Net::WebSocket::Frame::binary',
67              
68             FINISHED_INDICATOR => __PACKAGE__ . '::__ALREADY_SENT_FINAL',
69 1     1   4 };
  1         2  
70              
71             sub new {
72 18     18 0 1580 my ($class, $type) = @_;
73              
74 18         40 my $frame_class = $class->_load_frame_class($type);
75              
76 18         76 return bless { class => $frame_class, pid => $$ }, $class;
77             }
78              
79             sub create_chunk {
80 35     35 0 284 my $self = shift;
81              
82 35         107 my $frame = $self->{'class'}->new(
83             fin => 0,
84             $self->FRAME_MASK_ARGS(),
85             payload => \$_[0],
86             );
87              
88             #The first $frame we create needs to be typed (e.g., text or binary),
89             #but all subsequent ones must be continuation.
90 35 100       83 if ($self->{'class'} ne 'Net::WebSocket::Frame::continuation') {
91 18         33 $self->{'class'} = 'Net::WebSocket::Frame::continuation';
92             }
93              
94 35         99 return $frame;
95             }
96              
97             sub create_final {
98 18     18 0 133 my $self = shift;
99              
100 18         55 my $frame = $self->{'class'}->new(
101             fin => 1,
102             $self->FRAME_MASK_ARGS(),
103             payload => \$_[0],
104             );
105              
106 18         42 $self->{'finished'} = 1;
107              
108 18         45 return $frame;
109             }
110              
111             sub _load_frame_class {
112 18     18   33 my ($class, $type) = @_;
113              
114             #The old, legacy way of doing this. No longer documented,
115             #but it shipped in production so we’ll keep supporting it.
116 18         97 my $frame_class = $class->can("frame_class_$type");
117              
118 18 50       47 if ($frame_class) {
119 18         38 $frame_class = $frame_class->();
120             }
121             else {
122 0         0 require Net::WebSocket::FrameTypeName;
123 0         0 $frame_class = Net::WebSocket::FrameTypeName::get_module($type);
124             }
125              
126 18 100       82 Module::Runtime::require_module($frame_class) if !$frame_class->can('new');
127              
128 18         43 return $frame_class;
129             }
130              
131             sub DESTROY {
132 18     18   61 my ($self) = @_;
133              
134 18 50 33     97 if (($self->{'pid'} == $$) && !$self->{'finished'}) {
135 0         0 die Net::WebSocket::X->create('UnfinishedStream', $self);
136             }
137              
138 18         117 return;
139             }
140              
141             1;