stomp_parser 1.0.0-universal-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ %%{
2
+ machine frame;
3
+
4
+ getkey (chunk.getbyte(p) ^ 128) - 128;
5
+
6
+ action mark {
7
+ mark = p
8
+ }
9
+ action mark_key {
10
+ mark_key = chunk.byteslice(mark, p - mark)
11
+ mark = nil
12
+ }
13
+ action mark_frame {
14
+ mark_frame = Frame.new(nil, nil)
15
+ mark_frame_size = 0
16
+ }
17
+ action check_frame_size {
18
+ mark_frame_size += 1
19
+ raise FrameSizeExceeded if mark_frame_size > max_frame_size
20
+ }
21
+
22
+ action write_command {
23
+ mark_frame.write_command(chunk.byteslice(mark, p - mark))
24
+ mark = nil
25
+ }
26
+
27
+ action write_header {
28
+ mark_frame.write_header(mark_key, chunk.byteslice(mark, p - mark))
29
+ mark_key = mark = nil
30
+ }
31
+
32
+ action write_body {
33
+ mark_frame.write_body(chunk.byteslice(mark, p - mark))
34
+ mark = nil
35
+ }
36
+
37
+ action finish_headers {
38
+ mark_content_length = mark_frame.content_length
39
+ }
40
+
41
+ action consume_null {
42
+ (p - mark) < mark_content_length if mark_content_length
43
+ }
44
+
45
+ action consume_octet {
46
+ if mark_content_length
47
+ (p - mark) < mark_content_length
48
+ else
49
+ true
50
+ end
51
+ }
52
+
53
+ action finish_frame {
54
+ yield mark_frame
55
+ mark_frame = nil
56
+ }
57
+
58
+ include frame_common "parser_common.rl";
59
+ }%%
60
+
61
+ module StompParser
62
+ class RubyParser
63
+ class State
64
+ def initialize
65
+ @cs = RubyParser.start
66
+ @chunk = nil
67
+ @mark = nil
68
+ @mark_key = nil
69
+ @mark_frame = nil
70
+ @mark_frame_size = nil
71
+ @mark_content_length = nil
72
+ end
73
+
74
+ # You want documentation? HAHA.
75
+ attr_accessor :chunk
76
+ attr_accessor :cs
77
+ attr_accessor :mark
78
+ attr_accessor :mark_key
79
+ attr_accessor :mark_frame
80
+ attr_accessor :mark_frame_size
81
+ attr_accessor :mark_content_length
82
+ end
83
+
84
+ # this manipulates the singleton class of our context,
85
+ # so we do not want to run this code very often or we
86
+ # bust our ruby method caching
87
+ %% write data noprefix;
88
+
89
+ # Parse a chunk of Stomp-formatted data into a Frame.
90
+ #
91
+ # @param [String] chunk
92
+ # @param [State] state previous parser state, or nil for initial state
93
+ # @param [Integer] max_frame_size
94
+ # @yield [frame] yields each frame as it is parsed
95
+ # @yieldparam frame [Frame]
96
+ def self._parse(chunk, state, max_frame_size)
97
+ chunk.force_encoding(Encoding::BINARY)
98
+
99
+ if state.chunk
100
+ p = state.chunk.bytesize
101
+ chunk = state.chunk << chunk
102
+ else
103
+ p = 0
104
+ end
105
+
106
+ pe = chunk.bytesize # special
107
+
108
+ cs = state.cs
109
+ mark = state.mark
110
+ mark_key = state.mark_key
111
+ mark_frame = state.mark_frame
112
+ mark_frame_size = state.mark_frame_size
113
+ mark_content_length = state.mark_content_length
114
+
115
+ %% write exec;
116
+
117
+ if mark
118
+ state.chunk = chunk
119
+ else
120
+ state.chunk = nil
121
+ end
122
+
123
+ state.cs = cs
124
+ state.mark = mark
125
+ state.mark_key = mark_key
126
+ state.mark_frame = mark_frame
127
+ state.mark_frame_size = mark_frame_size
128
+ state.mark_content_length = mark_content_length
129
+
130
+ if cs == RubyParser.error
131
+ StompParser.build_parse_error(chunk, p)
132
+ else
133
+ nil
134
+ end
135
+ end
136
+
137
+ def initialize(max_frame_size = StompParser.max_frame_size)
138
+ @state = State.new
139
+ @max_frame_size = Integer(max_frame_size)
140
+ end
141
+
142
+ # Parse a chunk.
143
+ #
144
+ # @param [String] chunk
145
+ # @yield [frame]
146
+ # @yieldparam [Frame] frame
147
+ def parse(chunk)
148
+ @error ||= self.class._parse(chunk, @state, @max_frame_size) do |frame|
149
+ yield frame
150
+ end
151
+
152
+ raise @error if @error
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,3 @@
1
+ module StompParser
2
+ VERSION = "1.0.0"
3
+ end
data/parser_common.rl ADDED
@@ -0,0 +1,25 @@
1
+ %%{
2
+ machine frame_common;
3
+
4
+ NULL = "\0";
5
+ EOL = "\r"? . "\n";
6
+ OCTET = any;
7
+
8
+ client_commands = "SEND" | "SUBSCRIBE" | "UNSUBSCRIBE" | "BEGIN" | "COMMIT" | "ABORT" | "ACK" | "NACK" | "DISCONNECT" | "CONNECT" | "STOMP";
9
+ server_commands = "CONNECTED" | "MESSAGE" | "RECEIPT" | "ERROR";
10
+ command = (client_commands | server_commands) > mark % write_command . EOL;
11
+
12
+ HEADER_ESCAPE = "\\" . ("\\" | "n" | "r" | "c");
13
+ HEADER_OCTET = HEADER_ESCAPE | (OCTET - "\r" - "\n" - "\\" - ":");
14
+ header_key = HEADER_OCTET+ > mark % mark_key;
15
+ header_value = HEADER_OCTET* > mark;
16
+ header = header_key . ":" . header_value;
17
+ headers = (header % write_header . EOL)* % finish_headers . EOL;
18
+
19
+ consume_body = (NULL when consume_null | ^NULL when consume_octet)*;
20
+ body = consume_body >from(mark) % write_body <: NULL;
21
+
22
+ frame = ((command > mark_frame) :> headers :> (body @ finish_frame)) $ check_frame_size;
23
+
24
+ stream := (EOL | frame)*;
25
+ }%%
@@ -0,0 +1,67 @@
1
+ require "bundler/setup"
2
+ require "stomp_parser"
3
+ require "benchmark/ips"
4
+
5
+ class Benchpress
6
+ attr_reader :options
7
+
8
+ def initialize(options, &body)
9
+ @options = options
10
+ instance_exec(self, &body)
11
+ end
12
+
13
+ def name
14
+ "#{options[:file]}:#{options[:line]} #{options[:desc]}"
15
+ end
16
+
17
+ def setup(&block)
18
+ @setup = block
19
+ end
20
+
21
+ def code(&block)
22
+ @code = block
23
+ end
24
+
25
+ def assert(&block)
26
+ @assert = block
27
+ end
28
+
29
+ def run_initial
30
+ instance_exec(&@setup) if @setup
31
+ result = run
32
+ result = instance_exec(result, &@assert) if @assert
33
+ unless result
34
+ raise "#{name} code returns #{result.inspect}"
35
+ end
36
+ end
37
+
38
+ def run
39
+ instance_exec(&@code)
40
+ end
41
+
42
+ def to_proc
43
+ lambda { run }
44
+ end
45
+ end
46
+
47
+ def describe(description, &body)
48
+ file, line, _ = caller[0].split(':')
49
+ options = {
50
+ desc: description,
51
+ file: File.basename(file),
52
+ line: line,
53
+ }
54
+
55
+ $__benchmarks__ << Benchpress.new(options, &body)
56
+ end
57
+
58
+ $__benchmarks__ = []
59
+
60
+ at_exit do
61
+ reports = Benchmark.ips(time = 2) do |x|
62
+ $__benchmarks__.each do |bench|
63
+ 5.times { bench.run_initial }
64
+ x.report(bench.name, &bench)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ require_relative "../bench_helper"
2
+
3
+ describe "Frame#to_str minimal" do |bench|
4
+ bench.setup do
5
+ @frame = StompParser::Frame.new("CONNECT", nil)
6
+ end
7
+
8
+ bench.code { @frame.to_str }
9
+
10
+ bench.assert do |frame_str|
11
+ frame_str == "CONNECT\ncontent-length:0\n\n\x00"
12
+ end
13
+ end
14
+
15
+ describe "Frame#to_str with header" do |bench|
16
+ bench.setup do
17
+ @frame = StompParser::Frame.new("CONNECT", { "heart-beat" => "0,0" }, nil)
18
+ end
19
+
20
+ bench.code { @frame.to_str }
21
+
22
+ bench.assert do |frame_str|
23
+ frame_str == "CONNECT\nheart-beat:0,0\ncontent-length:0\n\n\x00"
24
+ end
25
+ end
26
+
27
+ describe "Frame#to_str with headers and small body" do |bench|
28
+ bench.setup do
29
+ @frame = StompParser::Frame.new("CONNECT", { "some" => "header" }, "body")
30
+ end
31
+
32
+ bench.code { @frame.to_str }
33
+
34
+ bench.assert do |frame_str|
35
+ frame_str == "CONNECT\nsome:header\ncontent-length:4\n\nbody\x00"
36
+ end
37
+ end
38
+
39
+ describe "Frame#to_str with headers and large body" do |bench|
40
+ bench.setup do
41
+ large_binary = "b\x00" * 2 # make room for headers
42
+ @frame = StompParser::Frame.new("CONNECT", { "some" => "header" }, large_binary)
43
+ end
44
+
45
+ bench.code { @frame.to_str }
46
+
47
+ bench.assert do |frame_str|
48
+ frame_str == "CONNECT\nsome:header\ncontent-length:#{@frame.body.bytesize}\n\n#{@frame.body}\x00"
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "../bench_helper"
2
+
3
+ def parse_one(parser, data)
4
+ frame = nil
5
+ parser.parse(data) { |m| frame = m }
6
+ frame
7
+ end
8
+
9
+ %w[CParser JavaParser RubyParser].each do |parser|
10
+ parser = begin
11
+ StompParser.const_get(parser)
12
+ rescue NameError
13
+ next
14
+ end
15
+
16
+ describe "#{parser}: minimal" do |bench|
17
+ bench.setup do
18
+ @parser = parser.new
19
+ @frame = "CONNECT\n\n\x00"
20
+ end
21
+
22
+ bench.code { parse_one(@parser, @frame) }
23
+ end
24
+
25
+ describe "#{parser}: headers and small body" do |bench|
26
+ bench.setup do
27
+ @parser = parser.new
28
+ @frame = "CONNECT\ncontent-length:4\n\nbody\x00"
29
+ end
30
+
31
+ bench.code { parse_one(@parser, @frame) }
32
+ end
33
+
34
+ describe "#{parser}: headers and large body" do |bench|
35
+ bench.setup do
36
+ @parser = parser.new
37
+ large_body = ("b" * (StompParser.max_frame_size - 50)) # make room for headers
38
+ @frame = "CONNECT\ncontent-length:#{large_body.bytesize}\n\n#{large_body}\x00"
39
+ end
40
+
41
+ bench.code { parse_one(@parser, @frame) }
42
+ end
43
+ end
data/spec/profile.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "bundler/setup"
2
+ require "stomp_parser"
3
+ require "perftools"
4
+
5
+ parser = StompParser::Parser.new
6
+ body_size = (1024 * 99) / 2
7
+ large_binary = "b\x00" * body_size # make room for headers
8
+ data = <<-MESSAGE
9
+ CONNECT
10
+ content-length:#{large_binary.bytesize}
11
+
12
+ #{large_binary}\x00
13
+ MESSAGE
14
+ stream = data + data + data + data
15
+
16
+ profile_output = File.expand_path("./profile/parser.profile", File.dirname(__FILE__))
17
+ PerfTools::CpuProfiler.start(profile_output) do
18
+ i = 100
19
+ loop do
20
+ parser.parse(data) do |frame|
21
+ # no op
22
+ end
23
+
24
+ i -= 1
25
+ break if i <= 0
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+ require "stomp_parser"
3
+ require "pry"
4
+ require "timeout"
5
+
6
+ require "support/shared_parser_examples"
@@ -0,0 +1,5 @@
1
+ if defined?(StompParser::CParser)
2
+ describe StompParser::CParser do
3
+ it_behaves_like "a stomp_parser parser"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ if defined?(StompParser::JavaParser)
2
+ describe StompParser::JavaParser do
3
+ it_behaves_like "a stomp_parser parser"
4
+ end
5
+ end
@@ -0,0 +1,50 @@
1
+ describe StompParser::Frame do
2
+ describe "#content_length" do
3
+ it "returns content length if available" do
4
+ frame = StompParser::Frame.new("CONNECT", { "content-length" => "1337" }, nil)
5
+ frame.content_length.should eq 1337
6
+ end
7
+
8
+ it "returns nil if no content length defined" do
9
+ frame = StompParser::Frame.new("CONNECT", nil)
10
+ frame.content_length.should be_nil
11
+ end
12
+
13
+ it "raises an error if invalid content length defined" do
14
+ frame = StompParser::Frame.new("CONNECT", { "content-length" => "LAWL" }, nil)
15
+ expect { frame.content_length }.to raise_error(StompParser::Error, /invalid content length "LAWL"/)
16
+ end
17
+ end
18
+
19
+ describe "#to_str" do
20
+ specify "frame with command only" do
21
+ frame = StompParser::Frame.new("CONNECT", nil)
22
+ frame.to_str.should eq "CONNECT\ncontent-length:0\n\n\x00"
23
+ end
24
+
25
+ specify "frame with with headers" do
26
+ frame = StompParser::Frame.new("CONNECT", { "moo" => "cow", "boo" => "hoo" }, nil)
27
+ frame.to_str.should eq "CONNECT\nmoo:cow\nboo:hoo\ncontent-length:0\n\n\x00"
28
+ end
29
+
30
+ specify "frame with with body" do
31
+ frame = StompParser::Frame.new("CONNECT", "this is a body")
32
+ frame.to_str.should eq "CONNECT\ncontent-length:14\n\nthis is a body\x00"
33
+ end
34
+
35
+ specify "frame with escapeable characters in headers" do
36
+ frame = StompParser::Frame.new("CONNECT", { "k\\\n\r:" => "v\\\n\r:" }, nil)
37
+ frame.to_str.should eq "CONNECT\nk\\\\\\n\\r\\c:v\\\\\\n\\r\\c\ncontent-length:0\n\n\x00"
38
+ end
39
+
40
+ specify "frame with binary body" do
41
+ frame = StompParser::Frame.new("CONNECT", "\x00ab\x00")
42
+ frame.to_str.should eq "CONNECT\ncontent-length:4\n\n\x00ab\x00\x00"
43
+ end
44
+
45
+ specify "overrides user-specified content-length" do
46
+ frame = StompParser::Frame.new("CONNECT", { "content-length" => "10" }, "\x00ab\x00")
47
+ frame.to_str.should eq "CONNECT\ncontent-length:4\n\n\x00ab\x00\x00"
48
+ end
49
+ end
50
+ end