File Coverage

blib/lib/XAS/Lib/Stomp/Parser.pm
Criterion Covered Total %
statement 12 84 14.2
branch 0 28 0.0
condition 0 10 0.0
subroutine 4 7 57.1
pod 2 3 66.6
total 18 132 13.6


line stmt bran cond sub pod time code
1             package XAS::Lib::Stomp::Parser;
2              
3             our $VERSION = '0.03';
4              
5 1     1   827 use XAS::Lib::Stomp::Frame;
  1         1  
  1         24  
6 1     1   3 use XAS::Constants ':stomp';
  1         1  
  1         5  
7              
8             use XAS::Class
9 1         8 debug => 0,
10             version => $VERSION,
11             base => 'XAS::Base',
12             mixin => 'XAS::Lib::Mixins::Bufops',
13             utils => 'trim',
14             vars => {
15             PARAMS => {
16             -target => { optional => 1, default => undef, regex => STOMP_LEVELS },
17             }
18             }
19 1     1   208 ;
  1         1  
20              
21              
22             my $HEADER = STOMP_HEADER;
23             my $CNTRL = STOMP_CNTRL;
24             my $BEOH = STOMP_BEOH;
25             my $EOF = STOMP_EOF;
26             my $EOH = STOMP_EOH;
27             my $EOL = STOMP_EOL;
28              
29             #use Data::Dumper;
30             #use Data::Hexdumper;
31              
32             # ----------------------------------------------------------------------
33             # Public Methods
34             # ----------------------------------------------------------------------
35              
36             sub parse {
37 0     0 0   my $self = shift;
38 0           my $buffer = shift;
39              
40 0           my $line;
41             my $length;
42 0           my $clength;
43 0           my $count = 0;
44 0           my $frame = undef;
45              
46 1     1   658 use bytes;
  1         1  
  1         7  
47              
48 0           $self->{buffer} .= $buffer;
49              
50 0           $self->log->debug('stomp-parser: begin');
51             # $self->log->debug(hexdump($self->{buffer}));
52              
53             # A valid frame is usually this:
54             #
55             # command<eol> - command
56             # header<eol> - 0 or more
57             # <eol> - seperator
58             # body<eof> - body
59             #
60             # as of v1.1 this is a valid frame
61             #
62             # <eol><eol><eol><eof>
63             #
64             # and is used as a NOOP, which is used as a protocol keepalive. This
65             # module will create a fake 'NOOP' command for that frame. Frame
66             # stringification does the right thing.
67             #
68             # All current versions define <eof> as \000.
69             #
70             # In v1.0 and v1.1, <eol> was defined as NEWLINE, which is \012, but,
71             # common usage was \n, which is platform specific, hence the $EOL, $EOH
72             # and $BEOH regexs to match against.
73             #
74             # v1.2 changes <eol> to \015\012
75             #
76              
77 0           for (;;) {
78              
79 0           $self->log->debug('stomp-parser: state = ' . $self->{state});
80              
81 0 0         if ($self->{state} eq 'command') {
    0          
    0          
    0          
82              
83             # start of the frame
84             # check for a valid buffer, must have a EOL someplace.
85              
86 0 0         if ($line = $self->buf_get_line(\$self->{buffer}, $EOL)) {
87              
88 0           $self->log->debug('stomp-parser: command');
89             # $self->log->debug(hexdump($line));
90              
91 0           $line = trim($line);
92              
93 0 0         $self->{command} = ($line eq '') ? 'NOOP' : $line;
94 0           $self->{state} = 'headers';
95              
96 0           } else { last; }
97              
98             } elsif ($self->{state} eq 'headers') {
99              
100             # start of the headers, they last until a standalone <eol>
101             # or <eof> is reached.
102              
103 0           $self->log->debug("stomp-parser: header");
104              
105 0           $length = length($self->{buffer});
106              
107 0           $self->{buffer} =~ m/$EOH/g;
108 0   0       $clength = pos($self->{buffer}) || -1;
109              
110 0           $self->log->debug("stomp-parser: end of headers, length: $clength");
111              
112 0 0         if ($clength == -1) {
113              
114 0           pos($self->{buffer}) = 0;
115 0           $self->{buffer} =~ m/$BEOH/g;
116 0   0       $clength = pos($self->{buffer}) || -1;
117              
118 0           $self->log->debug("stomp-parser: end of frame, length: $clength");
119              
120             }
121              
122 0 0 0       if (($clength != -1) && ($clength <= $length)) {
123              
124 0           $line = $self->buf_slurp(\$self->{buffer}, $clength);
125             # $self->log->debug(hexdump($line));
126              
127 0           while ($line =~ s/^$HEADER//) {
128              
129 0           $self->log->debug('stomp-parser: valid header');
130              
131 0           my $key = lc($1);
132 0           my $value = trim($2);
133              
134             # v1.2 says that the first defined header is
135             # to be honored. v1.0 and v1.1 implies that
136             # the last defined header is honored. The duplictes
137             # are discarded.
138              
139 0 0         if ($self->target < 1.2) {
140              
141 0           $self->{headers}->{$key} = $value;
142              
143             } else {
144              
145 0 0         unless (defined($self->{headers}->{$key})) {
146              
147 0           $self->{headers}->{$key} = $value;
148              
149             }
150              
151             }
152              
153 0           $line =~ s/$EOL//;
154              
155             }
156              
157 0           $self->{state} = 'body';
158              
159 0           } else { last; }
160              
161             } elsif ($self->{state} eq 'body') {
162              
163 0           $self->log->debug('stomp-parser: body');
164              
165             # start of the body, determine wither to use
166             # content-length or EOF to find the end
167              
168 0           $length = length($self->{buffer});
169              
170 0 0         if ($clength = $self->{headers}->{'content-length'}) {
171              
172 0           $self->log->debug('stomp-parser: using content-length');
173              
174 0 0         if ($clength <= $length) {
175              
176 0           $self->{body} = $self->buf_slurp(\$self->{buffer}, $clength);
177 0           $self->{state} = 'frame';
178              
179 0           } else { last; }
180              
181             } else {
182              
183 0           $self->log->debug('stomp-parser: using EOF');
184              
185 0           $clength = index($self->{buffer}, $EOF);
186              
187 0 0 0       if (($clength != -1) && ($clength <= $length)) {
188              
189 0           $self->{body} = $self->buf_get_line(\$self->{buffer}, $EOF);
190 0           chop $self->{body};
191 0           $self->{state} = 'frame';
192              
193 0           } else { last; }
194              
195             }
196              
197             } elsif ($self->{state} eq 'frame') {
198              
199 0           $self->log->debug('stomp-parser: building frame');
200              
201             # clear out inter-frame crap and create the object.
202              
203 0           $self->{buffer} =~ s/^$CNTRL//;
204              
205             $frame = XAS::Lib::Stomp::Frame->new(
206             -command => $self->{command},
207             -headers => $self->{headers},
208             -body => $self->{body}
209 0           );
210              
211             # reset ourselves
212              
213 0           $count = 0;
214              
215 0           delete $self->{command};
216 0           delete $self->{headers};
217 0           delete $self->{body};
218              
219 0           $self->{state} = 'command';
220              
221             }
222              
223             }
224              
225 0           return $frame;
226              
227             }
228              
229             sub get_pending {
230 0     0 1   my $self = shift;
231              
232 0           return $self->{buffer};
233              
234             }
235              
236             # ----------------------------------------------------------------------
237             # Private Methods
238             # ----------------------------------------------------------------------
239              
240             sub init {
241 0     0 1   my $class = shift;
242              
243 0           my $self = $class->SUPER::init(@_);
244              
245 0 0         unless (defined($self->{target})) {
246              
247 0           $self->{target} = $self->env->mqlevel;
248              
249             }
250              
251 0           $self->{state} = 'command';
252              
253 0           return $self;
254              
255             }
256              
257             1;
258              
259             __END__
260              
261             =head1 NAME
262              
263             XAS::Lib::Stomp::Parse - Create a STOMP Frame From a Buffer
264              
265             =head1 SYNOPSIS
266              
267             use XAS::Lib::Stomp::Parser;
268              
269             my $parser = XAS::Lib::Stomp::Parser->new();
270              
271             while (my $buffer = read()) {
272              
273             if (my $frame = $parser->parse($buffer)) {
274              
275             # do something...
276              
277             }
278              
279             }
280              
281             =head1 DESCRIPTION
282              
283             This module creates STOMP frames from a buffer. STOMP is the
284             Streaming Text Orientated Messaging Protocol (or the Protocol Briefly
285             Known as TTMP and Represented by the symbol :ttmp). It's a simple and easy to
286             implement protocol for working with Message Orientated Middleware from
287             any language. This module supports v1.0, v1.1 and v1.2 frames with limited
288             interoperability between the frame types.
289              
290             A STOMP frame consists of a command, a series of headers and a body.
291              
292             =head1 METHODS
293              
294             =head2 new
295              
296             Creates a new parser.
297              
298             =head2 get_pending
299              
300             Returns the contents of the internal buffer.
301              
302             =head1 SEE ALSO
303              
304             =over 4
305              
306             =item L<XAS|XAS>
307              
308             =back
309              
310             For more information on the STOMP protocol, please refer to: L<http://stomp.github.io/> .
311              
312             =head1 AUTHOR
313              
314             Kevin L. Esteb, E<lt>kevin@kesteb.usE<gt>
315              
316             =head1 COPYRIGHT AND LICENSE
317              
318             Copyright (C) 2014 Kevin L. Esteb
319              
320             This is free software; you can redistribute it and/or modify it under
321             the terms of the Artistic License 2.0. For details, see the full text
322             of the license at http://www.perlfoundation.org/artistic_license_2_0.
323              
324             =cut