File Coverage

blib/lib/Protocol/Redis/Faster.pm
Criterion Covered Total %
statement 71 73 97.2
branch 30 32 93.7
condition 14 14 100.0
subroutine 8 8 100.0
pod 4 4 100.0
total 127 131 96.9


line stmt bran cond sub pod time code
1             package Protocol::Redis::Faster;
2              
3 1     1   2365 use strict;
  1         3  
  1         28  
4 1     1   5 use warnings;
  1         2  
  1         24  
5 1     1   4 use Carp ();
  1         2  
  1         17  
6              
7 1     1   414 use parent 'Protocol::Redis';
  1         296  
  1         6  
8              
9             our $VERSION = '0.001';
10              
11             sub encode {
12 10     10 1 32 my ($self, $input) = @_;
13              
14 10         20 my @stack = $input;
15 10         16 my $encoded = '';
16 10         27 while (@stack) {
17 16         31 my $message = shift @stack;
18              
19             # Simple strings, errors, and integers
20 16 100 100     102 if ($message->{type} eq '+' or $message->{type} eq '-' or $message->{type} eq ':') {
    100 100        
    50          
21 3         13 $encoded .= $message->{type} . $message->{data} . "\r\n";
22             }
23              
24             # Bulk strings
25             elsif ($message->{type} eq '$') {
26 8         12 my $data = $message->{data};
27              
28 8 100       18 if (defined $data) {
29 6         20 $encoded .= '$' . length($data) . "\r\n" . $data . "\r\n";
30             }
31             else {
32 2         5 $encoded .= '$-1' . "\r\n";
33             }
34             }
35              
36             # Arrays
37             elsif ($message->{type} eq '*') {
38 5         8 my $data = $message->{data};
39              
40 5 100       12 if (defined $data) {
41 4         9 $encoded .= '*' . scalar(@$data) . "\r\n";
42 4         12 unshift @stack, @$data;
43             }
44             else {
45 1         4 $encoded .= '*-1' . "\r\n";
46             }
47             }
48              
49             # Invalid type
50             else {
51 0         0 Carp::croak(qq/Unknown message type $message->{type}/);
52             }
53             }
54              
55 10         49 return $encoded;
56             }
57              
58 26     26 1 67 sub get_message { shift @{$_[0]{_messages}} }
  26         197  
59              
60             sub on_message {
61 4     4 1 3907 my ($self, $cb) = @_;
62 4         14 $self->{_on_message_cb} = $cb;
63             }
64              
65             sub parse {
66 30     30 1 2758 my ($self, $input) = @_;
67 30         56 $self->{_buf} .= $input;
68              
69 30   100     75 my $curr = $self->{_curr} ||= {};
70 30         52 my $buf = \$self->{_buf};
71 30         54 my $cb = $self->{_on_message_cb};
72              
73             CHUNK:
74 30         64 while (length $$buf) {
75              
76             # Look for message type and get the actual data,
77             # length of the bulk string or the size of the array
78 45 100       84 if (!$curr->{type}) {
79 44         79 my $pos = index $$buf, "\r\n";
80 44 100       86 return if $pos < 0; # Wait for more data
81              
82 39         96 $curr->{type} = substr $$buf, 0, 1;
83 39         82 $curr->{len} = substr $$buf, 1, $pos - 1;
84 39         75 substr $$buf, 0, $pos + 2, ''; # Remove type + length/data + \r\n
85             }
86              
87             # Bulk strings
88 40 100 100     147 if ($curr->{type} eq '$') {
    100 100        
    50          
89 20 100       54 if ($curr->{len} == -1) {
    100          
90 1         2 $curr->{data} = undef;
91             }
92             elsif (length($$buf) - 2 < $curr->{len}) {
93 1         3 return; # Wait for more data
94             }
95             else {
96 18         41 $curr->{data} = substr $$buf, 0, $curr->{len}, '';
97             }
98              
99 19         31 substr $$buf, 0, 2, ''; # Remove \r\n
100             }
101              
102             # Simple strings, errors, and integers
103             elsif ($curr->{type} eq '+' or $curr->{type} eq '-' or $curr->{type} eq ':') {
104 12         27 $curr->{data} = delete $curr->{len};
105             }
106              
107             # Arrays
108             elsif ($curr->{type} eq '*') {
109 8 100       22 $curr->{data} = $curr->{len} < 0 ? undef : [];
110              
111             # Fill the array with data
112 8 100       21 if ($curr->{len} > 0) {
113 6         14 $curr = $self->{_curr} = {parent => $curr};
114 6         18 next CHUNK;
115             }
116             }
117              
118             # Invalid input
119             else {
120 0         0 Carp::croak(qq/Unexpected input "$curr->{type}"/);
121             }
122              
123             # Fill parent array with data
124 33         80 while (my $parent = delete $curr->{parent}) {
125 10         19 delete $curr->{len};
126 10         15 push @{$parent->{data}}, $curr;
  10         19  
127              
128 10 100       14 if (@{$parent->{data}} < $parent->{len}) {
  10         20  
129 4         10 $curr = $self->{_curr} = {parent => $parent};
130 4         13 next CHUNK;
131             }
132             else {
133 6         17 $curr = $self->{_curr} = $parent;
134             }
135             }
136              
137             # Emit a complete message
138 29         46 delete $curr->{len};
139 29 100       54 if (defined $cb) {
140 4         9 $cb->($self, $curr);
141             } else {
142 25         34 push @{$self->{_messages}}, $curr;
  25         49  
143             }
144 29         103 $curr = $self->{_curr} = {};
145             }
146             }
147              
148             1;
149              
150             =head1 NAME
151              
152             Protocol::Redis::Faster - Optimized pure-perl Redis protocol parser/encoder
153              
154             =head1 SYNOPSIS
155              
156             use Protocol::Redis::Faster;
157             my $redis = Protocol::Redis::Faster->new(api => 1) or die "API v1 not supported";
158              
159             $redis->parse("+foo\r\n");
160              
161             # get parsed message
162             my $message = $redis->get_message;
163             print "parsed message: ", $message->{data}, "\n";
164              
165             # asynchronous parsing interface
166             $redis->on_message(sub {
167             my ($redis, $message) = @_;
168             print "parsed message: ", $message->{data}, "\n";
169             });
170              
171             # parse pipelined message
172             $redis->parse("+bar\r\n-error\r\n");
173              
174             # create message
175             print "Get key message:\n",
176             $redis->encode({type => '*', data => [
177             {type => '$', data => 'string'},
178             {type => '+', data => 'OK'}
179             ]});
180              
181             =head1 DESCRIPTION
182              
183             This module implements the L API with more optimized pure-perl
184             internals. See L for usage documentation.
185              
186             This is a low level parsing module, if you are looking to use Redis in Perl,
187             try L, L, or L.
188              
189             =head1 BUGS
190              
191             Report any issues on the public bugtracker.
192              
193             =head1 AUTHORS
194              
195             Dan Book
196              
197             Jan Henning Thorsen
198              
199             =head1 CREDITS
200              
201             Thanks to Sergey Zasenko for the original L
202             and defining the API.
203              
204             =head1 COPYRIGHT AND LICENSE
205              
206             This software is Copyright (c) 2019 by Dan Book, Jan Henning Thorsen.
207              
208             This is free software, licensed under:
209              
210             The Artistic License 2.0 (GPL Compatible)
211              
212             =head1 SEE ALSO
213              
214             L