File Coverage

lib/HTTP/Promise/Stream/Brotli.pm
Criterion Covered Total %
statement 27 86 31.4
branch 0 28 0.0
condition 0 6 0.0
subroutine 9 15 60.0
pod 6 6 100.0
total 42 141 29.7


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Asynchronous HTTP Request and Promise - ~/lib/HTTP/Promise/Stream/Brotli.pm
3             ## Version v0.2.0
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2022/05/04
7             ## Modified 2023/09/08
8             ## All rights reserved.
9             ##
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package HTTP::Promise::Stream::Brotli;
15             BEGIN
16             {
17 2     2   2056 use strict;
  2         4  
  2         71  
18 2     2   12 use warnings;
  2         3  
  2         62  
19 2     2   11 use HTTP::Promise::Stream;
  2         7  
  2         19  
20 2     2   584 use parent -norequire, qw( HTTP::Promise::Stream::Generic );
  2         4  
  2         13  
21 2     2   103 use vars qw( @EXPORT_OK $VERSION $EXCEPTION_CLASS $BrotliError );
  2         6  
  2         164  
22             # use Nice::Try;
23             use constant {
24 2         192 ENCODE_BUFFER_SIZE => ( 32 * 1024 ),
25             DECODE_BUFFER_SIZE => ( 32 * 1024 ),
26 2     2   11 };
  2         7  
27 2     2   11 our @EXPORT_OK = qw( decode_bro encode_bro );
28 2         8 our $EXCEPTION_CLASS = 'HTTP::Promise::Exception';
29 2         38 our $VERSION = 'v0.2.0';
30             };
31              
32 2     2   15 use strict;
  2         8  
  2         46  
33 2     2   13 use warnings;
  2         6  
  2         1263  
34              
35             sub decode
36             {
37 0     0 1   my $self = shift( @_ );
38 0           my $from = shift( @_ );
39 0           my $to = shift( @_ );
40 0           my $opts = $self->_get_args_as_hash( @_ );
41 0           my( $from_fh, $reader ) = $self->_get_glob_from_arg( $from );
42 0           my( $to_fh, $writer ) = $self->_get_glob_from_arg( $to, write => 1 );
43 0 0 0       return( $self->pass_error ) if( !defined( $from_fh ) || !defined( $to_fh ) );
44 0           my( $n, $buff );
45 0 0         $self->_load_class( 'IO::Uncompress::Brotli', { no_import => 1 } ) || return( $self->pass_error );
46 0           my $c = IO::Uncompress::Brotli->create;
47            
48 0           while( $n = $reader->( $buff, DECODE_BUFFER_SIZE ) )
49             {
50 0           my $decoded = $c->decompress( $buff );
51             # try-catch
52 0           local $@;
53             my $rv = eval
54 0           {
55 0           $writer->( $decoded );
56             };
57 0 0         if( $@ )
58             {
59 0           return( $self->error( "Error decompressing with Brotli: $@" ) );
60             }
61 0 0         return( $self->pass_error ) if( !defined( $rv ) );
62             }
63 0 0         return( $self->pass_error ) if( !defined( $n ) );
64 0           return( $self );
65             }
66              
67             sub decode_bro
68             {
69 0     0 1   my $s = __PACKAGE__->new;
70 0           my $rv = $s->decode( @_ );
71 0 0         if( !defined( $rv ) )
72             {
73 0           $BrotliError = $s->error;
74 0           return;
75             }
76             else
77             {
78 0           undef( $BrotliError );
79 0           return( $rv );
80             }
81             }
82              
83             sub encode
84             {
85 0     0 1   my $self = shift( @_ );
86 0           my $from = shift( @_ );
87 0           my $to = shift( @_ );
88 0           my $opts = $self->_get_args_as_hash( @_ );
89 0           my( $from_fh, $reader ) = $self->_get_glob_from_arg( $from );
90 0           my( $to_fh, $writer ) = $self->_get_glob_from_arg( $to, write => 1 );
91 0 0 0       return( $self->pass_error ) if( !defined( $from_fh ) || !defined( $to_fh ) );
92 0           my( $n, $buff );
93 0 0         $self->_load_class( 'IO::Compress::Brotli', { no_import => 1 } ) || return( $self->pass_error );
94 0           my $c = IO::Compress::Brotli->create;
95            
96 0           while( $n = $reader->( $buff, ENCODE_BUFFER_SIZE ) )
97             {
98 0           my $encoded = $c->compress( $buff );
99             # try-catch
100 0           local $@;
101             my $rv = eval
102 0           {
103 0           $writer->( $encoded );
104             };
105 0 0         if( $@ )
106             {
107 0           return( $self->error( "Error compressing with Brotli: $@" ) );
108             }
109 0 0         return( $self->pass_error ) if( !defined( $rv ) );
110             }
111 0           $c->finish;
112              
113 0 0         return( $self->pass_error ) if( !defined( $n ) );
114 0           return( $self );
115             }
116              
117             sub encode_bro
118             {
119 0     0 1   my $s = __PACKAGE__->new;
120 0           my $rv = $s->encode( @_ );
121 0 0         if( !defined( $rv ) )
122             {
123 0           $BrotliError = $s->error;
124 0           return;
125             }
126             else
127             {
128 0           undef( $BrotliError );
129 0           return( $rv );
130             }
131             }
132              
133             sub is_decoder_installed
134             {
135 0     0 1   eval( 'use IO::Uncompress::Brotli ();' );
136 0 0         return( $@ ? 0 : 1 );
137             }
138              
139             sub is_encoder_installed
140             {
141 0     0 1   eval( 'use IO::Compress::Brotli ();' );
142 0 0         return( $@ ? 0 : 1 );
143             }
144              
145             # NOTE: sub FREEZE is inherited
146              
147             # NOTE: sub STORABLE_freeze is inherited
148              
149             # NOTE: sub STORABLE_thaw is inherited
150              
151             # NOTE: sub THAW is inherited
152              
153             1;
154             # NOTE: POD
155             __END__
156              
157             =encoding utf-8
158              
159             =head1 NAME
160              
161             HTTP::Promise::Stream::Brotli - Stream Encoder for Brotli Encoding
162              
163             =head1 SYNOPSIS
164              
165             use HTTP::Promise::Stream::Brotli;
166             my $s = HTTP::Promise::Stream::Brotli->new ||
167             die( HTTP::Promise::Stream::Brotli->error, "\n" );
168             $s->encode( $input => $output ) ||
169             die( $s->error );
170             $s->decode( $input => $output ) || die( $s->error );
171             HTTP::Promise::Stream::Brotli::encode_bro( $input => $output ) ||
172             die( $HTTP::Promise::Stream::Brotli::BrotliError );
173             HTTP::Promise::Stream::Brotli::decode_bro( $input => $output ) ||
174             die( $HTTP::Promise::Stream::Brotli::BrotliError );
175              
176             =head1 VERSION
177              
178             v0.2.0
179              
180             =head1 DESCRIPTION
181              
182             This implements an encoding and decoding mechanism for Brotli compression using either of the following on input and output:
183              
184             =over 4
185              
186             =item C<filepath>
187              
188             If the parameter is neither a scalar reference nor a file handle, it will be assumed to be a file path.
189              
190             =item C<file handle>
191              
192             This can be a native file handle, or an object oriented one as long as it implements the C<print> or C<write>, and C<read> methods. The C<read> method is expected to return the number of bytes read or C<undef> upon error. The C<print> and C<write> methods are expected to simply return true upon success and C<undef> upon error.
193              
194             Alternatively, those methods can die and those exceptions wil be caught.
195              
196             =item C<scalar reference>
197              
198             This can be a simple scalar reference, or an object scalar reference.
199              
200             =back
201              
202             This module requires L<IO::Compress::Brotli> and L<IO::Uncompress::Brotli> to be installed or it will return an error.
203              
204             Brotli L<is described|https://www.brotli.org/> as "Brotli is a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling" that is "similar in speed with deflate" and "supported by L<most web browsers|http://caniuse.com/#search=brotli>, major web servers, and some CDNs". The specification of its Compressed Data Format is defined in L<rfc7932|https://tools.ietf.org/html/rfc7932>.
205              
206             According to L<Mozilla|https://developer.mozilla.org/en-US/docs/Glossary/brotli_compression>, "Brotli provides better compression ratios than gzip and deflate speeds are comparable, but brotli compressing is a slower process than Gzip"
207              
208             =head1 CONSTRUCTOR
209              
210             =head2 new
211              
212             Creates a new L<HTTP::Promise::Stream::Brotli> object and returns it.
213              
214             =head1 METHODS
215              
216             =head2 decode
217              
218             This takes 2 arguments: an input and an output. Each one can be either a file path, a file handle, or a scalar reference.
219              
220             It will decode the Brotli encoded data and write the result into the output.
221              
222             It returns true upon success and sets an L<error|Module::Generic/error> and return C<undef> upon error.
223              
224             =head2 encode
225              
226             This takes 2 arguments: an input and an output. Each one can be either a file path, a file handle, or a scalar reference.
227              
228             It will encode the data into Brotli encoded data and write the result into the output.
229              
230             It returns true upon success and sets an L<error|Module::Generic/error> and return C<undef> upon error.
231              
232             =head1 CLASS FUNCTIONS
233              
234             The following class functions are available and can also be exported, such as:
235              
236             use HTTP::Promise::Stream::Brotli qw( decode_bro encode_bro );
237              
238             =head2 decode_bro
239              
240             This takes the same 2 arguments used in L</decode>: an input and an output. Each one can be either a file path, a file handle, or a scalar reference.
241              
242             It will decode the Brotli encoded data and write the result into the output.
243              
244             It returns true upon success, and upon error, it will set the error in the global variable C<$BrotliError> and return C<undef>
245              
246             my $decoded = HTTP::Promise::Stream::Brotli::decode_bro( $encoded );
247             die( "Something went wrong: $HTTP::Promise::Stream::Brotli::BrotliError\n" if( !defined( $decoded ) );
248             print( "Decoded data is: $decoded\n" );
249              
250             =head2 encode_bro
251              
252             This takes the same 2 arguments used in L</encode>: an input and an output. Each one can be either a file path, a file handle, or a scalar reference.
253              
254             It will encode the data into Brotli encoded data and write the result into the output.
255              
256             It returns true upon success, and upon error, it will set the error in the global variable C<$BrotliError> and return C<undef>
257              
258             my $encoded = HTTP::Promise::Stream::Brotli::encode_bro( $data );
259             die( "Something went wrong: $HTTP::Promise::Stream::Brotli::BrotliError\n" if( !defined( $encoded ) );
260             print( "Encoded data is: $encoded\n" );
261              
262             =head2 is_decoder_installed
263              
264             Returns true if the module L<IO::Uncompress::Brotli> is installed, false otherwise.
265              
266             =head2 is_encoder_installed
267              
268             Returns true if the module L<IO::Compress::Brotli> is installed, false otherwise.
269              
270             =head1 AUTHOR
271              
272             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
273              
274             =head1 SEE ALSO
275              
276             L<IO::Compress::Brotli>, L<IO::Uncompress::Brotli>
277              
278             L<Brotli web page|https://www.brotli.org/>, L<Brotli Github page|https://github.com/google/brotli>
279              
280             L<rfc7932|https://tools.ietf.org/html/rfc7932>
281              
282             L<Wikipedia page|https://en.wikipedia.org/wiki/Brotli>
283              
284             L<caniuse|http://caniuse.com/#search=brotli>
285              
286             L<HTTP::Promise>, L<HTTP::Promise::Request>, L<HTTP::Promise::Response>, L<HTTP::Promise::Message>, L<HTTP::Promise::Entity>, L<HTTP::Promise::Headers>, L<HTTP::Promise::Body>, L<HTTP::Promise::Body::Form>, L<HTTP::Promise::Body::Form::Data>, L<HTTP::Promise::Body::Form::Field>, L<HTTP::Promise::Status>, L<HTTP::Promise::MIME>, L<HTTP::Promise::Parser>, L<HTTP::Promise::IO>, L<HTTP::Promise::Stream>, L<HTTP::Promise::Exception>
287              
288             =head1 COPYRIGHT & LICENSE
289              
290             Copyright(c) 2022 DEGUEST Pte. Ltd.
291              
292             All rights reserved.
293              
294             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
295              
296             =cut