nebulous_stomp 2.0.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.hgignore +2 -0
  3. data/.hgtags +1 -0
  4. data/README.md +225 -28
  5. data/feature/connection_example.yaml +24 -0
  6. data/feature/feature_test_spec.rb +247 -0
  7. data/feature/gimme.rb +91 -0
  8. data/lib/nebulous_stomp/listener.rb +107 -0
  9. data/lib/nebulous_stomp/message.rb +132 -265
  10. data/lib/nebulous_stomp/msg/body.rb +169 -0
  11. data/lib/nebulous_stomp/msg/header.rb +98 -0
  12. data/lib/nebulous_stomp/param.rb +16 -35
  13. data/lib/nebulous_stomp/redis_handler.rb +19 -29
  14. data/lib/nebulous_stomp/redis_handler_null.rb +12 -11
  15. data/lib/nebulous_stomp/redis_helper.rb +110 -0
  16. data/lib/nebulous_stomp/request.rb +212 -0
  17. data/lib/nebulous_stomp/stomp_handler.rb +30 -96
  18. data/lib/nebulous_stomp/stomp_handler_null.rb +8 -22
  19. data/lib/nebulous_stomp/target.rb +52 -0
  20. data/lib/nebulous_stomp/version.rb +1 -1
  21. data/lib/nebulous_stomp.rb +63 -50
  22. data/md/LICENSE.txt +20 -2
  23. data/md/nebulous_protocol.md +25 -18
  24. data/spec/listener_spec.rb +104 -0
  25. data/spec/message_spec.rb +227 -116
  26. data/spec/nebulous_spec.rb +44 -9
  27. data/spec/param_spec.rb +16 -33
  28. data/spec/redis_handler_null_spec.rb +0 -2
  29. data/spec/redis_handler_spec.rb +0 -2
  30. data/spec/redis_helper_spec.rb +107 -0
  31. data/spec/request_spec.rb +249 -0
  32. data/spec/stomp_handler_null_spec.rb +33 -34
  33. data/spec/stomp_handler_spec.rb +1 -74
  34. data/spec/target_spec.rb +97 -0
  35. metadata +20 -11
  36. data/lib/nebulous_stomp/nebrequest.rb +0 -259
  37. data/lib/nebulous_stomp/nebrequest_null.rb +0 -37
  38. data/spec/nebrequest_null_spec.rb +0 -219
  39. data/spec/nebrequest_spec.rb +0 -239
  40. data/spec/through_test_spec.rb +0 -80
@@ -0,0 +1,169 @@
1
+ require 'json'
2
+
3
+ require_relative '../stomp_handler'
4
+
5
+
6
+ module NebulousStomp
7
+ module Msg
8
+
9
+
10
+ ##
11
+ # A class to encapsulate a Nebulous message body - helper class for Message
12
+ #
13
+ class Body
14
+
15
+ # Might be nil: only caught on messages that came directly from STOMP.
16
+ attr_reader :stomp_body
17
+
18
+ # The Nebulous Protocol
19
+ attr_reader :verb, :params, :desc
20
+
21
+ # Will be a hash for messages that follow The Protocol; anything at all otherwise.
22
+ attr_reader :body
23
+
24
+ ##
25
+ # is_json should be a boolean, true if the body is JSON-encoded.
26
+ # If it is false then we assume that we are coded like STOMP headers, in lines of text.
27
+ #
28
+ def initialize(is_json, hash)
29
+ @is_json = !!is_json
30
+ @stomp_body = hash[:stompBody]
31
+ @body = hash[:body]
32
+ @verb = hash[:verb]
33
+ @params = hash[:parameters] || hash[:params]
34
+ @desc = hash[:description] || hash[:desc]
35
+
36
+ fill_from_stomp
37
+ end
38
+
39
+ ##
40
+ # Output a the body part of the hash for serialization to the cache.
41
+ #
42
+ # Since the body could be quite large we only set :body if there is no @stomp_body. We
43
+ # recreate the one from the other anyway.
44
+ #
45
+ def to_h
46
+ { stompBody: @stomp_body,
47
+ body: @stomp_body ? nil : body,
48
+ verb: @verb,
49
+ params: @params.kind_of?(Enumerable) ? @params.dup : @params,
50
+ desc: @desc }
51
+
52
+ end
53
+
54
+ ##
55
+ # Return a body object for the Stomp gem
56
+ #
57
+ def body_for_stomp
58
+
59
+ case
60
+ when @is_json
61
+ @body.to_json
62
+ when @body.is_a?(Hash)
63
+ @body.map{|k,v| "#{k}: #{v}" }.join("\n") << "\n\n"
64
+ else
65
+ @body.to_s
66
+ end
67
+
68
+ end
69
+
70
+ ##
71
+ # Return the message body formatted for The Protocol, in JSON.
72
+ #
73
+ # Raise an exception if the message body doesn't follow the protocol.
74
+ #
75
+ # (We use this as the key for the Redis cache)
76
+ #
77
+ def protocol_json
78
+ fail NebulousError, "no protocol in this message!" unless @verb
79
+ protocol_hash.to_json
80
+ end
81
+
82
+ private
83
+
84
+ ##
85
+ # Return The Protocol of the message as a hash.
86
+ #
87
+ def protocol_hash
88
+ { verb: @verb,
89
+ parameters: @params,
90
+ description: @desc }.delete_if{|_,v| v.nil? }
91
+
92
+ end
93
+
94
+ ##
95
+ # Fill all the other attributes, if we can.
96
+ #
97
+ # Note that we confusingly store @verb, @params, & @desc (The Protocol, which if present is
98
+ # the message body); @body (the message body); and @stomp_body (the original body from the
99
+ # stomp message if we were created from one). Any of these could be passed in the initialize
100
+ # hash.
101
+ #
102
+ # The rule is that we always prioritize them in that order. If we are passed a @verb, then
103
+ # the Protocol fields go into @body; otherwise if set we take @body as it stands; otherwise
104
+ # we try and decode @stomp_body and set that in @body.
105
+ #
106
+ # NB: We never overwrite @stomp_body. If not passed to us, it stays nil. It's only stored in
107
+ # case we can't decode it, as a fallback.
108
+ #
109
+ def fill_from_stomp
110
+
111
+ if @verb
112
+ @body = protocol_hash
113
+ elsif @body.nil? || @body.respond_to?(:empty?) && @body.empty?
114
+ @body = parse_stomp_body
115
+ end
116
+
117
+ parse_body
118
+
119
+ # Assume that if verb is missing, the other two are just part of a
120
+ # response which has nothing to do with the protocol
121
+ @params = @desc = nil unless @verb
122
+
123
+ self
124
+ end
125
+
126
+ def parse_stomp_body
127
+ case
128
+ when @stomp_body.nil? then nil
129
+ when @is_json then stomp_body_from_json
130
+ else
131
+ stomp_body_from_text
132
+
133
+ end
134
+ end
135
+
136
+ def stomp_body_from_json
137
+ JSON.parse(@stomp_body)
138
+ rescue JSON::ParserError, TypeError
139
+ # If we can't parse it, fine, take it as a text blob
140
+ @stomp_body.to_s
141
+ end
142
+
143
+ def stomp_body_from_text
144
+ lines = @stomp_body.to_s.split("\n").reject{|s| s =~ /^\s*$/ }
145
+ hash = {}
146
+
147
+ lines.each do |line|
148
+ k,v = line.split(':', 2).each{|x| x.strip! }
149
+ hash[k] = v if line.include?(':')
150
+ end
151
+
152
+ # If there are any lines we could not parse, forget the whole thing and return a text blob
153
+ (lines.size > hash.keys.size) ? @stomp_body.to_s : hash
154
+ end
155
+
156
+ def parse_body
157
+ if @body.is_a?(Hash)
158
+ @verb ||= @body["verb"]
159
+ @params ||= @body["parameters"] || @body["params"]
160
+ @desc ||= @body["description"] || @body["desc"]
161
+ end
162
+ end
163
+
164
+
165
+ end # Message::Body
166
+
167
+
168
+ end
169
+ end
@@ -0,0 +1,98 @@
1
+ require 'json'
2
+
3
+ require_relative '../stomp_handler'
4
+
5
+
6
+ module NebulousStomp
7
+ module Msg
8
+
9
+
10
+ ##
11
+ # A class to encapsulate a Nebulous message header - a helper class for Message
12
+ #
13
+ class Header
14
+
15
+ # Might be nil: parsed from incoming messages; set by StompHandler on send
16
+ attr_accessor :reply_id
17
+
18
+ # Might be nil: only caught on messages that came directly from STOMP
19
+ attr_reader :stomp_headers
20
+
21
+ # The content type of the message
22
+ attr_reader :content_type
23
+
24
+ # From The Nebulous Protocol
25
+ attr_reader :reply_to, :in_reply_to
26
+
27
+ ##
28
+ #
29
+ def initialize(hash)
30
+ @stomp_headers = hash[:stompHeaders]
31
+ @reply_to = hash[:replyTo]
32
+ @reply_id = hash[:replyId]
33
+ @in_reply_to = hash[:inReplyTo]
34
+ @content_type = hash[:contentType]
35
+
36
+ # If we have no stomp headers then we (probably correctly) assume that this is a user
37
+ # created message, and default the content type to JSON.
38
+ @content_type = 'application/json' if @stomp_headers.nil? && @content_type.nil?
39
+
40
+ fill_from_stomp
41
+ end
42
+
43
+ ##
44
+ # true if the content type appears to be JSON-y
45
+ #
46
+ def content_is_json?
47
+ @content_type =~ /json$/i ? true : false
48
+ end
49
+
50
+ ##
51
+ # Output a the header part of the hash for serialization to the cache.
52
+ #
53
+ def to_h
54
+ { stompHeaders: @stomp_headers,
55
+ replyTo: @reply_to,
56
+ replyId: @reply_id,
57
+ inReplyTo: @in_reply_to,
58
+ contentType: @content_type }
59
+
60
+ end
61
+
62
+ ##
63
+ # Return the hash of additional headers for the Stomp gem
64
+ #
65
+ def headers_for_stomp
66
+ { "content-type" => @content_type,
67
+ "neb-reply-id" => @reply_id,
68
+ "neb-reply-to" => @reply_to,
69
+ "neb-in-reply-to" => @in_reply_to }
70
+
71
+ end
72
+
73
+ private
74
+
75
+ ##
76
+ # Fill all the other attributes, if you can, from @stomp_headers
77
+ #
78
+ def fill_from_stomp
79
+ return unless @stomp_headers
80
+
81
+ type = @stomp_headers["content-type"] || @stomp_headers[:'content-type'] \
82
+ || @stomp_headers["content_type"] || @stomp_headers[:content_type] \
83
+ || @stomp_headers["contentType"] || @stomp_headers[:contentType]
84
+
85
+ @content_type ||= type
86
+ @reply_id ||= @stomp_headers['neb-reply-id']
87
+ @reply_to ||= @stomp_headers['neb-reply-to']
88
+ @in_reply_to ||= @stomp_headers['neb-in-reply-to']
89
+
90
+ self
91
+ end
92
+
93
+ end # of Header
94
+
95
+
96
+ end
97
+ end
98
+
@@ -1,3 +1,6 @@
1
+ require_relative 'target'
2
+
3
+
1
4
  module NebulousStomp
2
5
 
3
6
 
@@ -7,7 +10,6 @@ module NebulousStomp
7
10
  module Param
8
11
  extend self
9
12
 
10
-
11
13
  # Default parameters hash
12
14
  ParamDefaults = { stompConnectHash: {},
13
15
  redisConnectHash: {},
@@ -21,66 +23,50 @@ module NebulousStomp
21
23
  receiveQueue: nil,
22
24
  messageTimeout: nil }
23
25
 
24
-
25
26
  ##
26
- # Set the initial parameter string. This also has the effect of resetting
27
- # everything.
27
+ # Set the initial parameter string. This also has the effect of resetting everything.
28
28
  #
29
- # Parameters default to Param::ParamDefaults. keys passed in parameter p to
30
- # ovveride those defaults must match, or a NebulousError will result.
29
+ # Parameters default to Param::ParamDefaults. keys passed in parameter p to override those
30
+ # defaults must match, or a NebulousError will result.
31
31
  #
32
32
  # This method is only called by Nebulous::init().
33
33
  #
34
34
  def set(p={})
35
- raise NebulousError, "Invalid initialisation hash" unless p.kind_of?(Hash)
35
+ fail NebulousError, "Invalid initialisation hash" unless p.kind_of?(Hash)
36
36
 
37
37
  validate(ParamDefaults, p, "Unknown initialisation hash")
38
38
 
39
39
  @params = ParamDefaults.merge(p)
40
40
  end
41
41
 
42
-
43
42
  ##
44
43
  # Add a Nebulous target. Raises NebulousError if anything looks screwy.
45
44
  #
46
45
  # Parameters:
47
- # n -- target name
48
- # t -- hash, which must follow the pattern set by Param::TargetDefaults
46
+ # * t -- a Target
49
47
  #
50
48
  # Used only by Nebulous::init
51
49
  #
52
- def add_target(n, t)
53
- raise NebulousError, "Invalid target hash" unless t.kind_of?(Hash)
54
-
55
- validate(TargetDefaults, t, "Unknown target hash")
56
-
57
- raise NebulousError, "Config Problem - Target missing 'send'" \
58
- if t[:sendQueue].nil?
59
-
60
- raise NebulousError, "Config Problem - Target missing 'receive'" \
61
- if t[:receiveQueue].nil?
50
+ def add_target(t)
51
+ fail NebulousError, "Invalid target" unless t.kind_of?(Target)
62
52
 
63
53
  @params ||= ParamDefaults
64
- @params[:targets][n.to_sym] = TargetDefaults.merge(t)
54
+ @params[:targets][t.name.to_sym] = t
65
55
  end
66
56
 
67
-
68
-
69
57
  ##
70
58
  # Set a logger instance
71
59
  #
72
60
  def set_logger(lg)
73
- raise NebulousError unless lg.kind_of?(Logger) || lg.nil?
61
+ fail NebulousError unless lg.kind_of?(Logger) || lg.nil?
74
62
  @logger = lg
75
63
  end
76
64
 
77
-
78
65
  ##
79
66
  # Get the logger instance
80
67
  #
81
68
  def get_logger; @logger; end
82
69
 
83
-
84
70
  ##
85
71
  # Get the whole parameter hash. Probably only useful for testing.
86
72
  #
@@ -88,7 +74,6 @@ module NebulousStomp
88
74
  @params
89
75
  end
90
76
 
91
-
92
77
  ##
93
78
  # Get a the value of the parameter with the key p.
94
79
  #
@@ -97,18 +82,15 @@ module NebulousStomp
97
82
  @params[p.to_sym]
98
83
  end
99
84
 
100
-
101
85
  ##
102
- # Given a target name, return the corresponding target hash
86
+ # Given a target name, return the corresponding Target object
103
87
  #
104
88
  def get_target(name)
105
89
  t = Param.get(:targets)
106
- x = (t && t.kind_of?(Hash)) ? t[name.to_sym] : nil
107
-
108
- raise NebulousError, "Config problem - unknown target #{name}" if x.nil?
109
- return x
90
+ (t && t.kind_of?(Hash)) ? t[name.to_s.to_sym] : nil
110
91
  end
111
92
 
93
+ private
112
94
 
113
95
  ##
114
96
  # Raise an exception if a hash has any keys not found in an exemplar
@@ -117,11 +99,10 @@ module NebulousStomp
117
99
  #
118
100
  def validate(exemplar, hash, message)
119
101
  hash.each_key do |k|
120
- raise NebulousError, "#{message} key '#{k}'" unless exemplar.include?(k)
102
+ fail NebulousError, "#{message} key '#{k}'" unless exemplar.include?(k)
121
103
  end
122
104
  end
123
105
 
124
-
125
106
  ##
126
107
  # reset all parameters -- probably only useful for testing
127
108
  #
@@ -6,48 +6,44 @@ module NebulousStomp
6
6
 
7
7
  ##
8
8
  # A class to deal with talking to Redis via the Redis gem
9
+ #
10
+ # You shouldn't need to instantiate this yourself. If you want to use NebulousStomp to talk to
11
+ # redis directly, try NebulousStomp::RedisHelper.
9
12
  #
10
13
  class RedisHandler
11
14
 
12
- # This is most likely useless to anything except spec/redis_handler_spec --
13
- # at least, I hope so...
15
+ # This is most likely useless to anything except spec/redis_handler_spec -- at least, I hope
16
+ # so...
14
17
  attr_reader :redis
15
18
 
16
-
17
19
  ##
18
20
  # Initialise an instance of the handler by passing it the connection hash
19
21
  #
20
22
  # If no hash is passed, we try and get it from Nebulous::Params
21
23
  #
22
- # We use the optional testRedis parameter to mock connections to Redis, for
23
- # testing. It's probably of no use to anyone else.
24
+ # We use the optional testRedis parameter to mock connections to Redis, for testing. It's
25
+ # probably of no use to anyone else.
24
26
  #
25
27
  def initialize(connectHash=nil, testRedis=nil)
26
- @redis_hash = connectHash ? connectHash.dup : nil
27
- @redis_hash ||= Param.get(:redisConnectHash)
28
-
28
+ @redis_hash = connectHash ? connectHash.dup : nil
29
29
  @test_redis = testRedis
30
30
  @redis = nil
31
31
  end
32
32
 
33
-
34
33
  ##
35
- # Connect to the Redis key/value store. Raise Nebulous error if connection
36
- # fails.
34
+ # Connect to the Redis key/value store. Raise Nebulous error if connection fails.
37
35
  #
38
36
  def connect
39
37
  @redis = @test_redis || Redis.new(@redis_hash.dup)
40
38
 
41
39
  @redis.client.connect
42
- raise ConnectionError, "Redis Connection failed" unless @redis.connected?
40
+ fail ConnectionError, "Redis Connection failed" unless @redis.connected?
43
41
 
44
42
  self
45
-
46
43
  rescue => err
47
44
  raise ConnectionError, err.to_s
48
45
  end
49
46
 
50
-
51
47
  ##
52
48
  # :call-seq:
53
49
  # handler.connected? -> (boolean)
@@ -60,7 +56,6 @@ module NebulousStomp
60
56
  self
61
57
  end
62
58
 
63
-
64
59
  ##
65
60
  # return whether we are connected to Redis.
66
61
  #
@@ -68,38 +63,33 @@ module NebulousStomp
68
63
  @redis && @redis.connected?
69
64
  end
70
65
 
71
-
72
66
  ##
73
67
  # :call-seq:
74
68
  # handler.redis_on? -> (boolean)
75
69
  #
76
- # Return whether the Redis is turned "on" in the connect hash, the config
77
- # file.
70
+ # Return whether the Redis is turned "on" in the connect hash, the config file.
78
71
  #
79
- # The rest of nebulous should just let RedisHandler worry about this
80
- # detail.
72
+ # The rest of nebulous should just let RedisHandler worry about this detail.
81
73
  #
82
74
  def redis_on?
83
75
  @redis_hash && !@redis_hash.empty?
84
76
  end
85
77
 
86
-
87
-
88
78
  ##
89
- # Cover all the other methods on @redis that we are basically forwarding to
90
- # it. I could use Forwardable here -- except that would not allow us to
91
- # raise Nebulous::ConnectionError if @redis.nil?
79
+ # Cover all the other methods on @redis that we are basically forwarding to it. I could use
80
+ # Forwardable here -- except that would not allow us to raise Nebulous::ConnectionError if
81
+ # @redis.nil?
82
+ #
83
+ # Possible candidates for future inclusion: exists, expire, ping
92
84
  #
93
85
  def method_missing(meth, *args)
94
86
  super unless [:set,:get,:del].include?(meth)
95
-
96
- raise ConnectionError, "Redis not connected, sent #{meth}" \
97
- unless connected?
87
+ fail ConnectionError, "Redis not connected, sent #{meth}" unless connected?
98
88
 
99
89
  @redis.__send__(meth, *args)
100
90
  end
101
91
 
102
- end
92
+ end # RedisHandler
103
93
 
104
94
 
105
95
  end
@@ -6,7 +6,6 @@ require_relative 'redis_handler'
6
6
 
7
7
  module NebulousStomp
8
8
 
9
-
10
9
  ##
11
10
  # Behaves just like RedisHandler, except, does nothing and expects no
12
11
  # connection to Redis.
@@ -17,41 +16,43 @@ module NebulousStomp
17
16
 
18
17
  attr_reader :fake_pair
19
18
 
20
-
21
19
  def initialize(connectHash={})
22
20
  super
23
21
  @fake_pair = {}
24
22
  end
25
23
 
26
-
27
24
  def insert_fake(key, value)
28
25
  @fake_pair = { key => value }
29
26
  end
30
27
 
31
-
32
28
  def connect
33
29
  @redis = true
34
30
  self
35
31
  end
36
32
 
37
-
38
33
  def quit
39
34
  @redis = nil
40
35
  self
41
36
  end
42
-
43
37
 
44
38
  def connected?
45
39
  @fake_pair != {}
46
40
  end
47
41
 
42
+ def set(key, value, hash=nil)
43
+ insert_fake(key, value)
44
+ "OK"
45
+ end
48
46
 
49
- def set(key, value, hash=nil); insert_fake(key, value); end
50
-
51
- def del(key); @fake_pair = {}; end
52
-
53
- def get(key); @fake_pair.values.first; end
47
+ def del(key)
48
+ x = @fake_pair.empty? ? 0 : 1
49
+ @fake_pair = {}
50
+ x
51
+ end
54
52
 
53
+ def get(key)
54
+ @fake_pair.values.first
55
+ end
55
56
 
56
57
  end
57
58
 
@@ -0,0 +1,110 @@
1
+ require 'json'
2
+
3
+ require_relative '../nebulous_stomp'
4
+
5
+
6
+ module NebulousStomp
7
+
8
+
9
+ ##
10
+ # A class to help out users who want to talk to Redis themselves; the "Redis use case".
11
+ #
12
+ # redis = NebulousStomp::RedisHelper.new
13
+ #
14
+ # redis.set(:thing, "thingy")
15
+ # redes.set(:gone_in_30_seconds, "thingy", 30)
16
+ #
17
+ # value = redis.get(:thing)
18
+ #
19
+ # redis.del(:thing)
20
+ #
21
+ class RedisHelper
22
+
23
+ # For testing only
24
+ attr_writer :redis_handler
25
+
26
+ ##
27
+ # :call-seq:
28
+ # RedisHelper.new -> (helper)
29
+ #
30
+ def initialize
31
+ @param_hash = Param.get(:redisConnectHash)
32
+
33
+ fail NebulousError, "NebulousStomp.init has not been called or Redis not configured" \
34
+ if @param_hash.nil? || @param_hash.empty?
35
+
36
+ end
37
+
38
+ ##
39
+ # :call-seq:
40
+ # redis.set(key, value)
41
+ # redis.set(key, value, timeout)
42
+ #
43
+ # Set a value in the store.
44
+ #
45
+ def set(key, value, timeout=nil)
46
+ rtimeout = (Integer(timeout.to_s, 10) rescue nil)
47
+ rvalue = value_to_json(value)
48
+ fail ArgumentError, "Timeout must be a number" if timeout && rtimeout.nil?
49
+ ensure_connected
50
+
51
+ if timeout
52
+ redis_handler.set(key.to_s, rvalue, ex: rtimeout)
53
+ else
54
+ redis_handler.set(key.to_s, rvalue)
55
+ end
56
+
57
+ self
58
+ end
59
+
60
+ ##
61
+ # :call-seq:
62
+ # redis.get(key) -> value
63
+ #
64
+ # Get a string value from the store. Return nil if there is none.
65
+ #
66
+ def get(key)
67
+ ensure_connected
68
+ json_to_value(redis_handler.get key.to_s)
69
+ end
70
+
71
+ ##
72
+ # :call-seq:
73
+ # redis.del(key)
74
+ #
75
+ # Remove a value from the store. Raise an exception if there is none.
76
+ #
77
+ def del(key)
78
+ ensure_connected
79
+ num = redis_handler.del(key.to_s)
80
+ fail ArgumentError, "Unknown key, cannot delete" if num == 0
81
+ end
82
+
83
+ private
84
+
85
+ def redis_handler
86
+ @redis_handler ||= RedisHandler.new(Param.get :redisConnectHash)
87
+ end
88
+
89
+ def ensure_connected
90
+ redis_handler.connect unless redis_handler.connected?
91
+ end
92
+
93
+ def value_to_json(value)
94
+ { value: value}.to_json
95
+ end
96
+
97
+ def json_to_value(json)
98
+ return nil if json.nil?
99
+ hash = JSON.parse(json, symbolize_names: true)
100
+
101
+ hash.is_a?(Hash) ? hash[:value] : nil
102
+ rescue JSON::ParserError
103
+ return nil
104
+ end
105
+
106
+ end # RedisHelper
107
+
108
+
109
+ end
110
+