redis-protocol 0.1.2 → 0.1.3
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.
- data/VERSION +1 -1
- data/lib/redis-protocol.rb +2 -4
- data/lib/redis-protocol/request.rb +14 -110
- data/lib/redis-protocol/response.rb +22 -0
- data/lib/redis-protocol/unified_protocol.rb +56 -0
- data/spec/redis-protocol/response_spec.rb +22 -0
- data/spec/redis-protocol/unified_protocol_spec.rb +34 -0
- metadata +7 -5
- data/lib/redis-protocol/static_request.rb +0 -77
- data/spec/redis-protocol/static_request_spec.rb +0 -54
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/lib/redis-protocol.rb
CHANGED
@@ -1,134 +1,38 @@
|
|
1
|
+
require_relative 'unified_protocol'
|
2
|
+
|
1
3
|
class RedisProtocol::Request
|
2
|
-
attr_reader :raw_data, :type, :
|
4
|
+
attr_reader :raw_data, :type, :components
|
3
5
|
|
4
6
|
def initialize(data)
|
5
7
|
@raw_data = data
|
6
|
-
@type, @components
|
8
|
+
@type, @components= nil, []
|
7
9
|
@next_length = 0
|
8
10
|
parse
|
9
11
|
end
|
10
12
|
|
13
|
+
def component_count
|
14
|
+
@components.length
|
15
|
+
end
|
16
|
+
|
11
17
|
private
|
12
18
|
|
13
19
|
def parse
|
14
|
-
@raw_data.
|
15
|
-
@type ||= c.eql?('*') ? :standard : :inline
|
20
|
+
@type ||= @raw_data[0].eql?('*') ? :standard : :inline
|
16
21
|
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
complete_token
|
21
|
-
@component_count += @components.count if @type == :inline
|
22
|
+
@components = send(@type)
|
22
23
|
end
|
23
24
|
=begin
|
24
25
|
Inline Parser
|
25
26
|
=end
|
26
|
-
def inline
|
27
|
-
|
28
|
-
|
29
|
-
if c.empty? and not @current.empty?
|
30
|
-
complete_token
|
31
|
-
else
|
32
|
-
@current += c
|
33
|
-
end
|
27
|
+
def inline
|
28
|
+
@raw_data.split
|
34
29
|
end
|
35
30
|
|
36
31
|
=begin
|
37
32
|
Standard Parser
|
38
33
|
=end
|
39
34
|
|
40
|
-
def standard
|
41
|
-
@
|
42
|
-
@doubt_str = ''
|
43
|
-
|
44
|
-
send(@token_type, char)
|
45
|
-
end
|
46
|
-
|
47
|
-
def args_counts(char)
|
48
|
-
return if char.eql? '*'
|
49
|
-
|
50
|
-
tokenize char do
|
51
|
-
@component_count = @current.to_i
|
52
|
-
clean_context
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def length(char)
|
57
|
-
return if char.eql? '$'
|
58
|
-
|
59
|
-
tokenize char do
|
60
|
-
@next_length = @current.to_i
|
61
|
-
clean_context
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def payload(char)
|
66
|
-
return if char.eql? '$'
|
67
|
-
|
68
|
-
@next_length = 0 if @current.length == @next_length
|
69
|
-
|
70
|
-
tokenize char do
|
71
|
-
@next_length = 0
|
72
|
-
complete_token
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def tokenize(char, &block)
|
77
|
-
reach_delimiter = consume_delimiter(char)
|
78
|
-
|
79
|
-
if reach_delimiter
|
80
|
-
if @doubt_str.empty?
|
81
|
-
block.call
|
82
|
-
end
|
83
|
-
return
|
84
|
-
end
|
85
|
-
|
86
|
-
@current += char
|
87
|
-
end
|
88
|
-
|
89
|
-
=begin
|
90
|
-
Common Utilities
|
91
|
-
=end
|
92
|
-
|
93
|
-
def complete_token
|
94
|
-
@components << @current
|
95
|
-
clean_context
|
96
|
-
end
|
97
|
-
|
98
|
-
def clean_context
|
99
|
-
@current = ''
|
100
|
-
@token_type = nil
|
101
|
-
end
|
102
|
-
|
103
|
-
def consume_delimiter(char)
|
104
|
-
return false if @next_length != 0
|
105
|
-
|
106
|
-
if @doubt and char.eql? "\n"
|
107
|
-
@doubt_str = ''
|
108
|
-
@doubt = false
|
109
|
-
return true
|
110
|
-
end
|
111
|
-
|
112
|
-
if not @doubt and char.eql? "\r"
|
113
|
-
@doubt_str = "\r"
|
114
|
-
@doubt = true
|
115
|
-
return true
|
116
|
-
end
|
117
|
-
|
118
|
-
@doubt = false
|
119
|
-
@current += @doubt_str unless @doubt_str.empty?
|
120
|
-
@doubt_str = ''
|
121
|
-
return false
|
122
|
-
end
|
123
|
-
|
124
|
-
def detect_token_type(char)
|
125
|
-
case char
|
126
|
-
when '*'
|
127
|
-
:args_counts
|
128
|
-
when '$'
|
129
|
-
:length
|
130
|
-
else
|
131
|
-
:payload
|
132
|
-
end
|
35
|
+
def standard
|
36
|
+
RedisProtocol::UnifiedProtocol.parse @raw_data
|
133
37
|
end
|
134
38
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'unified_protocol'
|
2
|
+
|
3
|
+
class RedisProtocol::Response
|
4
|
+
attr_reader :result, :raw_data
|
5
|
+
|
6
|
+
def <<(value)
|
7
|
+
@result += value.result
|
8
|
+
unless value.result.empty?
|
9
|
+
start = @raw_data.index('*') + 1
|
10
|
+
finish = @raw_data.index(RedisProtocol::UnifiedProtocol::DELIMITER)
|
11
|
+
@raw_data[start...finish] = @result.length.to_s
|
12
|
+
|
13
|
+
@raw_data += value.raw_data[(value.raw_data.index(RedisProtocol::UnifiedProtocol::DELIMITER) + RedisProtocol::UnifiedProtocol::DELIMITER.length)..-1]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(data)
|
18
|
+
@raw_data = data
|
19
|
+
@raw_data += RedisProtocol::UnifiedProtocol::DELIMITER unless @raw_data.end_with? RedisProtocol::UnifiedProtocol::DELIMITER
|
20
|
+
@result = RedisProtocol::UnifiedProtocol.parse @raw_data
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class RedisProtocol::UnifiedProtocol
|
2
|
+
DELIMITER="\r\n"
|
3
|
+
class << self
|
4
|
+
def parse(data)
|
5
|
+
processed, index = 0, data.index(DELIMITER)
|
6
|
+
index ||= data.length
|
7
|
+
result = case data[processed]
|
8
|
+
when '*'
|
9
|
+
parse_multi_chunked data
|
10
|
+
when '$'
|
11
|
+
parse_chunked data
|
12
|
+
when '+'
|
13
|
+
parse_status data
|
14
|
+
when '-'
|
15
|
+
parse_error data
|
16
|
+
when ':'
|
17
|
+
parse_integer data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_multi_chunked(data)
|
22
|
+
index = data.index DELIMITER
|
23
|
+
count = data[1...index].to_i
|
24
|
+
result = []
|
25
|
+
start = index + DELIMITER.length
|
26
|
+
1.upto(count) do |_|
|
27
|
+
chunk, length = parse_chunked data, start
|
28
|
+
start = length + DELIMITER.length
|
29
|
+
result += chunk
|
30
|
+
end
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_chunked(data, start=0)
|
36
|
+
index = data.index DELIMITER, start
|
37
|
+
length = data[(start+1)...index].to_i
|
38
|
+
return nil if length == -1
|
39
|
+
result = [data[index+DELIMITER.length, length]]
|
40
|
+
|
41
|
+
start == 0 ? result : [result, index+DELIMITER.length + length]
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_status(data)
|
45
|
+
[true, data[1..-1]]
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_error(data)
|
49
|
+
[false, data[1..-1]]
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_integer(data)
|
53
|
+
[data[1..-1].to_i]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RedisProtocol::Response do
|
4
|
+
it 'should be parse response' do
|
5
|
+
result = RedisProtocol::Response.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n"
|
6
|
+
result.result[0].should eql 'set'
|
7
|
+
result.result[1].should eql 'test'
|
8
|
+
result.result[2].should eql 'hello'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should be parse aggregated response' do
|
12
|
+
result = RedisProtocol::Response.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n"
|
13
|
+
additional = RedisProtocol::Response.new "*2\r\n$5\r\nworld\r\n$6\r\nturtle\r\n"
|
14
|
+
result << additional
|
15
|
+
result.result[0].should eql 'set'
|
16
|
+
result.result[1].should eql 'test'
|
17
|
+
result.result[2].should eql 'hello'
|
18
|
+
result.result[3].should eql 'world'
|
19
|
+
result.result[4].should eql 'turtle'
|
20
|
+
result.raw_data.should eql "*5\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n$5\r\nworld\r\n$6\r\nturtle\r\n"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RedisProtocol::UnifiedProtocol do
|
4
|
+
it 'should parse a redis unified protocol based message' do
|
5
|
+
result = RedisProtocol::UnifiedProtocol.parse "$4\r\nping"
|
6
|
+
result[0].should eql 'ping'
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should parse multi-chunked format' do
|
10
|
+
result = RedisProtocol::UnifiedProtocol.parse "*1\r\n$4\r\nping"
|
11
|
+
result[0].should eql 'ping'
|
12
|
+
result = RedisProtocol::UnifiedProtocol.parse "*2\r\n$4\r\nkeys\r\n$1\r\n*\r\n"
|
13
|
+
result[0].should eql 'keys'
|
14
|
+
result[1].should eql '*'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should parse status ok' do
|
18
|
+
result = RedisProtocol::UnifiedProtocol.parse '+ok'
|
19
|
+
result[0].should be true
|
20
|
+
result[1].should eql 'ok'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should parse error with message' do
|
24
|
+
result = RedisProtocol::UnifiedProtocol.parse "-ERR unknown command 'foobar'"
|
25
|
+
result[0].should be false
|
26
|
+
result[1].should eql "ERR unknown command 'foobar'"
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should parse integer only return' do
|
30
|
+
result = RedisProtocol::UnifiedProtocol.parse ":1000\r\n"
|
31
|
+
result[0].should be 1000
|
32
|
+
RedisProtocol::UnifiedProtocol.parse(':-30')[0].should be -30
|
33
|
+
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-protocol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -127,9 +127,11 @@ files:
|
|
127
127
|
- VERSION
|
128
128
|
- lib/redis-protocol.rb
|
129
129
|
- lib/redis-protocol/request.rb
|
130
|
-
- lib/redis-protocol/
|
130
|
+
- lib/redis-protocol/response.rb
|
131
|
+
- lib/redis-protocol/unified_protocol.rb
|
131
132
|
- spec/redis-protocol/request_spec.rb
|
132
|
-
- spec/redis-protocol/
|
133
|
+
- spec/redis-protocol/response_spec.rb
|
134
|
+
- spec/redis-protocol/unified_protocol_spec.rb
|
133
135
|
- spec/redis-protocol_spec.rb
|
134
136
|
- spec/spec_helper.rb
|
135
137
|
homepage: http://github.com/astral1/redis-protocol
|
@@ -147,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
149
|
version: '0'
|
148
150
|
segments:
|
149
151
|
- 0
|
150
|
-
hash:
|
152
|
+
hash: -1569199417732497117
|
151
153
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
154
|
none: false
|
153
155
|
requirements:
|
@@ -1,77 +0,0 @@
|
|
1
|
-
class RedisProtocol::StaticRequest
|
2
|
-
attr_reader :raw_data, :type, :components, :component_count
|
3
|
-
OP_DELIMITER = "\r\n"
|
4
|
-
|
5
|
-
def initialize(data)
|
6
|
-
@raw_data = data
|
7
|
-
parse_request
|
8
|
-
end
|
9
|
-
|
10
|
-
def parse_request
|
11
|
-
@type = check_request_type
|
12
|
-
|
13
|
-
send @type
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.recognize(data)
|
17
|
-
(check_request_type == :inline) ? data.split[0] : data.split(OP_DELIMITER)[2]
|
18
|
-
end
|
19
|
-
|
20
|
-
def check_request_type
|
21
|
-
(@raw_data.start_with? '*') ? :standard : :inline
|
22
|
-
end
|
23
|
-
|
24
|
-
def standard
|
25
|
-
components = []
|
26
|
-
data = @raw_data.dup
|
27
|
-
operand_length, payload = operand_length_for data.split(OP_DELIMITER)
|
28
|
-
1.upto(operand_length).each do |_|
|
29
|
-
data, payload = unpack_payload(payload)
|
30
|
-
components << data
|
31
|
-
end
|
32
|
-
@component_count = components.count
|
33
|
-
@components = components
|
34
|
-
end
|
35
|
-
|
36
|
-
def operand_length_for(data)
|
37
|
-
operand_length, payload = next_token data
|
38
|
-
raise "invalid packet : #{operand_length}" unless operand_length.start_with? '*'
|
39
|
-
operand_length = operand_length[1..-1].to_i
|
40
|
-
|
41
|
-
[operand_length, payload]
|
42
|
-
end
|
43
|
-
|
44
|
-
def unpack_payload(payload)
|
45
|
-
field_length, payload = next_token payload
|
46
|
-
raise "invalid length format : #{field_length}" unless field_length.start_with? '$'
|
47
|
-
field_length = field_length[1..-1].to_i
|
48
|
-
next_token payload, field_length
|
49
|
-
end
|
50
|
-
|
51
|
-
def inline
|
52
|
-
@components = @raw_data.split
|
53
|
-
@component_count = @components.count
|
54
|
-
@components
|
55
|
-
end
|
56
|
-
|
57
|
-
def next_token(data, length = 0)
|
58
|
-
if data[0].length == length || length == 0
|
59
|
-
[data[0], data[1..-1]]
|
60
|
-
else
|
61
|
-
multiline_token(data, length)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def multiline_token(data, length)
|
66
|
-
val, index = '', 0
|
67
|
-
data.each do |token|
|
68
|
-
val.empty? ? val = token : val += "\r\n#{token}"
|
69
|
-
index += 1
|
70
|
-
|
71
|
-
break if val.length == length
|
72
|
-
end
|
73
|
-
[val, data[index..-1]]
|
74
|
-
end
|
75
|
-
|
76
|
-
private :next_token, :standard, :inline, :operand_length_for, :unpack_payload, :multiline_token
|
77
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe RedisProtocol::StaticRequest do
|
4
|
-
it 'initialize with data' do
|
5
|
-
RedisProtocol::StaticRequest.new 'ping'
|
6
|
-
end
|
7
|
-
|
8
|
-
it 'should get raw data as original' do
|
9
|
-
request = RedisProtocol::StaticRequest.new 'ping'
|
10
|
-
request.raw_data.should eql 'ping'
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'should parse inline command' do
|
14
|
-
request = RedisProtocol::StaticRequest.new 'ping'
|
15
|
-
request.components[0].should eql 'ping'
|
16
|
-
request.component_count.should be 1
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'should parse inline command with parameter' do
|
20
|
-
request = RedisProtocol::StaticRequest.new 'keys *'
|
21
|
-
request.components[0].should eql 'keys'
|
22
|
-
request.component_count.should be 2
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'should parse inline command with multi whitespaces' do
|
26
|
-
request = RedisProtocol::StaticRequest.new 'keys *'
|
27
|
-
request.components[0].should eql 'keys'
|
28
|
-
request.component_count.should be 2
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'should parse standard command' do
|
32
|
-
request = RedisProtocol::StaticRequest.new "*1\r\n$4\r\nping"
|
33
|
-
request.components[0].should eql 'ping'
|
34
|
-
request.component_count.should be 1
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'should parse standard command with parameter' do
|
38
|
-
request = RedisProtocol::StaticRequest.new "*2\r\n$4\r\nkeys\r\n$1\r\n*"
|
39
|
-
request.components[0].should eql 'keys'
|
40
|
-
request.component_count.should be 2
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'should parse standard command with multiple parameters' do
|
44
|
-
request = RedisProtocol::StaticRequest.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello"
|
45
|
-
request.components[0].should eql 'set'
|
46
|
-
request.component_count.should be 3
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'should parse standard command containing delimiter' do
|
50
|
-
request = RedisProtocol::StaticRequest.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$12\r\nhello\r\nworld"
|
51
|
-
request.components[2].should eql "hello\r\nworld"
|
52
|
-
request.component_count.should be 3
|
53
|
-
end
|
54
|
-
end
|