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 - 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 subclass
49             this module and define C constants, where C<*> is the
50             frame type, e.g., C, C.
51              
52             =cut
53              
54 1     1   253 use strict;
  1         2  
  1         34  
55 1     1   5 use warnings;
  1         2  
  1         19  
56              
57 1     1   253 use Net::WebSocket::Frame::continuation ();
  1         3  
  1         20  
58 1     1   5 use Net::WebSocket::X ();
  1         2  
  1         27  
59              
60             use constant {
61              
62             #These can be overridden in subclasses.
63 1         314 frame_class_text => 'Net::WebSocket::Frame::text',
64             frame_class_binary => 'Net::WebSocket::Frame::binary',
65              
66             FINISHED_INDICATOR => __PACKAGE__ . '::__ALREADY_SENT_FINAL',
67 1     1   5 };
  1         2  
68              
69             sub new {
70 18     18 0 1394 my ($class, $type) = @_;
71              
72 18         305 my $frame_class = $class->_load_frame_class($type);
73              
74 18         73 return bless { class => $frame_class, pid => $$ }, $class;
75             }
76              
77             sub create_chunk {
78 35     35 0 178 my $self = shift;
79              
80 35         76 my $frame = $self->{'class'}->new(
81             fin => 0,
82             $self->FRAME_MASK_ARGS(),
83             payload_sr => \$_[0],
84             );
85              
86             #The first $frame we create needs to be typed (e.g., text or binary),
87             #but all subsequent ones must be continuation.
88 35 100       87 if ($self->{'class'} ne 'Net::WebSocket::Frame::continuation') {
89 18         22 $self->{'class'} = 'Net::WebSocket::Frame::continuation';
90             }
91              
92 35         74 return $frame;
93             }
94              
95             sub create_final {
96 18     18 0 73 my $self = shift;
97              
98 18         36 my $frame = $self->{'class'}->new(
99             $self->FRAME_MASK_ARGS(),
100             fin => 1,
101             payload_sr => \$_[0],
102             );
103              
104 18         32 $self->{'finished'} = 1;
105              
106 18         37 return $frame;
107             }
108              
109             sub _load_frame_class {
110 18     18   32 my ($class, $type) = @_;
111              
112 18         75 my $frame_class = $class->can("frame_class_$type");
113 18 50       52 if (!$frame_class) {
114 0         0 die "Unknown frame type: “$type”!";
115             }
116              
117 18         35 $frame_class = $frame_class->();
118 18 100       62 if (!$frame_class->can('new')) {
119 1         5 Module::Load::load($frame_class);
120             }
121              
122 18         41 return $frame_class;
123             }
124              
125             sub DESTROY {
126 18     18   46 my ($self) = @_;
127              
128 18 50 33     71 if (($self->{'pid'} == $$) && !$self->{'finished'}) {
129 0         0 die Net::WebSocket::X->create('UnfinishedStream', $self);
130             }
131              
132 18         119 return;
133             }
134              
135             1;