nebulous_stomp 2.0.2 → 3.0.0

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.
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
@@ -6,48 +6,51 @@ require 'devnull'
6
6
  require 'nebulous_stomp/version'
7
7
  require 'nebulous_stomp/param'
8
8
  require 'nebulous_stomp/message'
9
- require 'nebulous_stomp/nebrequest'
9
+ require 'nebulous_stomp/target'
10
+ require 'nebulous_stomp/listener'
11
+ require 'nebulous_stomp/request'
10
12
  require 'nebulous_stomp/stomp_handler'
11
13
  require 'nebulous_stomp/redis_handler'
12
14
 
13
15
 
14
16
  ##
15
- # A little module that implements the Nebulous Protocol, a way of passing data
16
- # over STOMP between different systems. We also support message cacheing via
17
- # Redis.
17
+ # NebulousStomp
18
+ # =============
18
19
  #
19
- # There are two use cases:
20
+ # A little module that implements The Nebulous Protocol, a way of passing data over STOMP between
21
+ # different systems. Specifically, it allows you to send a message, a *Request*, and receive another
22
+ # message in answer, a *Response*. (Which is not something STOMP does, out of the box).
20
23
  #
21
- # First, sending a request for information and waiting for a response, which
22
- # might come from a cache of previous responses, if you allow it. To do
23
- # this you should create a Nebulous::NebRequest, which will return a
24
- # Nebulous::Message.
24
+ # This library covers two specific use cases (three if you are picky):
25
25
  #
26
- # Second, the other end of the deal: hanging around waiting for requests and
27
- # sending responses. To do this, you need to use the Nebulous::StompHandler
28
- # class, which will again furnish Nebulous::Meessage objects, and allow you to
29
- # create them.
26
+ # 1) Request-Response: a program that consumes incoming messages, works out what message to
27
+ # send in response, and sends it.
30
28
  #
31
- # Some configuratuion is required: see Nebulous.init, Nebulous.add_target &
32
- # Nebulous.add_logger.
29
+ # 2) Question-Answer: a program that sends a request and then waits for a response; the other
30
+ # end of the Request-Response use case. We support optional caching of responses in Redis,
31
+ # to speed things up if your program is likely to make the same request repeatedly within a
32
+ # short time.
33
33
  #
34
- # Since you are setting the Redis connection details as part of initialisation,
35
- # you can also use it to connect to Redis, if you want. See
36
- # Nebulous::RedisHandler.
34
+ # 3) Since we are talking to Redis, we expose a basic, simple interface for you to talk to it
35
+ # yourself.
37
36
  #
38
- # a complete list of classes & modules:
37
+ # These are the externally-facing classes:
39
38
  #
40
- # * NebulousStomp
41
- # * NebulousStomp::Param
42
- # * NebulousStomp::NebRequest
43
- # * NebulousStomp::NebRequestNull
44
- # * NebulousStomp::Message
45
- # * NebulousStomp::StompHandler
46
- # * NebulousStomp::StompHandlerNull
47
- # * NebulousStomp::RedisHandler
48
- # * NebulousStomp::RedisHandlerNull
39
+ # * Listener -- implements the request-response use case
40
+ # * Message -- a Nebulous-Stomp message
41
+ # * NebulousStomp -- main class
42
+ # * RedisHelper -- implements the Redis use case
43
+ # * Request -- implements the Request-Response use case; a wrapper for Message
44
+ # * Target -- represents a single Target
45
+ #
46
+ # These classes are used internally:
47
+ #
48
+ # * Param -- helper class to store and return configuration
49
+ # * RedisHandler -- internal class to wrap the Redis gem
50
+ # * RedisHandlerNull -- a "mock" version of RedisHandler for use in testing
51
+ # * StompHandler -- internal class to wrap the Stomp gem
52
+ # * StompHandlerNull -- a "mock" version of StompHandler for use in testing
49
53
  #
50
- # If you want the null classes, you must require them seperately.
51
54
  module NebulousStomp
52
55
 
53
56
 
@@ -60,56 +63,67 @@ module NebulousStomp
60
63
  # Thrown when we can't connect to STOMP or the connection is lost somehow
61
64
  class ConnectionError < NebulousError; end
62
65
 
63
-
66
+ ##
64
67
  # :call-seq:
65
- # Nebulous.init(paramHash) -> (nil)
68
+ # NebulousStomp.init(paramHash) -> (nil)
66
69
  #
67
- # Initialise library for use and override default options with any in
68
- # <paramHash>.
70
+ # Initialise library for use and override default options with any in <paramHash>.
69
71
  #
70
72
  # The default options are defined in Nebulous::Param.
71
73
  #
72
74
  def self.init(paramHash={})
73
75
  Param.set(paramHash)
74
- return nil
76
+ nil
75
77
  end
76
78
 
77
-
79
+ ##
78
80
  # :call-seq:
79
- # Nebulous.add_target(name, targetHash) -> (nil)
81
+ # NebulousStomp.add_target(name, targetHash) -> Target
82
+ #
83
+ # Add a Nebulous target called <name> with a details as per <targetHash>.
80
84
  #
81
- # Add a nebulous target called <name> with a details as per <targetHash>.
85
+ # <targetHash> must contain a send queue and a receive queue, or a NebulousError will be
86
+ # raised. Have a look in NebulousStomp::Target for the default hash you are overriding here.
82
87
  #
83
- # <targetHash> must contain a send queue and a receive queue, or a
84
- # NebulousError will be thrown. Have a look in Nebulous::Param for the
85
- # default hash you are overriding here.
88
+ # Note that Param expects the target hash to have a :name key. We don't; we add it in.
86
89
  #
87
- def self.add_target(name, targetHash) # -> nil
88
- Param.add_target(name, targetHash)
89
- return nil
90
+ def self.add_target(name, targetHash)
91
+ t = NebulousStomp::Target.new targetHash.merge(name: name)
92
+ Param.add_target(t)
93
+ t
90
94
  end
91
95
 
96
+ ##
97
+ # :call-seq:
98
+ # NebulousStomp.get_target(name) # -> Target
99
+ #
100
+ # Given a target name, return the Target object.
101
+ #
102
+ def self.get_target(name)
103
+ Param.get_target(name)
104
+ end
92
105
 
93
106
  ##
107
+ # :call-seq:
108
+ # NebulousStomp.set_logger(Logger.new STDOUT)
109
+ #
94
110
  # Set an instance of Logger to log stuff to.
111
+ #
95
112
  def self.set_logger(logger)
96
113
  Param.set_logger(logger)
97
114
  end
98
115
 
99
-
100
116
  ##
101
117
  # :call-seq:
102
- # Nebulous.logger.info(__FILE__) { "message" }
118
+ # NebulousStomp.logger.info(__FILE__) { "message" }
103
119
  #
104
- # Return a Logger instance to log things to.
105
- # If one was not given to Param, return a logger instance that
106
- # uses a DevNull IO object, that is, goes nowhere.
120
+ # Return a Logger instance to log things to. If one was not given to Param, return a logger
121
+ # instance that uses a DevNull IO object, that is, goes nowhere.
107
122
  #
108
123
  def self.logger
109
124
  Param.get_logger || Logger.new( DevNull.new )
110
125
  end
111
126
 
112
-
113
127
  ##
114
128
  # :call-seq:
115
129
  # Nebulous.on? -> Boolean
@@ -121,7 +135,6 @@ module NebulousStomp
121
135
  !(h.nil? || h.empty?)
122
136
  end
123
137
 
124
-
125
138
  ##
126
139
  # :call-seq:
127
140
  # Nebulous.redis_on? -> Boolean
data/md/LICENSE.txt CHANGED
@@ -1,3 +1,21 @@
1
- Copyright (c) 2015 James Hall & Co Ltd
1
+ The MIT License (MIT)
2
2
 
3
- Permission to use copy etc etc withheld. No-one is reading this, but.
3
+ Copyright (c) 2016 Andrew Jones
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -6,8 +6,10 @@ Nebulous: The Protocol
6
6
 
7
7
  Introduction
8
8
  ------------
9
- Project Nebulous is a STOMP client written in ABL, and also the protocol that allows our different
10
- systems, ABL or not, to pass messages. This document, obviously, concentrates on the protocol.
9
+ This document defines some specific rules of engagement for processes to communicate using STOMP.
10
+ It so happens that NebulousStomp is currently the only open-source thing to use these rules -- I
11
+ made them up -- but technically speaking, that's a "protocol", and I'm going to call it such
12
+ because you can't stop me.
11
13
 
12
14
  The basic idea is this: one system passes a message, a _request_; and then waits for an answer to
13
15
  that message, a _response_, from another system. STOMP doesn't explicitly support that; the
@@ -20,7 +22,8 @@ message is both a response and a request, and the terms get rather less useful.)
20
22
 
21
23
  The Protocol
22
24
  ------------
23
- Let's start with the actual protocol, the rules. They won't make sense without reading the rest of the document, but:
25
+ Let's start with the actual protocol, the rules. They won't make sense without reading the rest of
26
+ the document, but:
24
27
 
25
28
  1. Every valid request (that is, every message with an identifiable verb other than "success" or
26
29
  "error") will always generate a response. If the handling routine cannot generate a valid
@@ -45,7 +48,7 @@ Let's start with the actual protocol, the rules. They won't make sense without r
45
48
  6. A given queue that is the target of requests within The Protocol will be for only one system or
46
49
  common group of systems. They may consume (ACK) messages sent to it even if they do not have the
47
50
  facility to process them. If multiple systems share a queue, they should understand that
48
- messages will be consumed from it at random.
51
+ messages will be consumed from it at random. (So, don't do that.)
49
52
 
50
53
 
51
54
  Components
@@ -74,9 +77,9 @@ The Protocol specifies a format for the message body. It consists of three field
74
77
 
75
78
  * _description_ is a text field and can contain anything. It is optional.
76
79
 
77
- Nebulous supports message bodies in either JSON (in which case parameters is probably an array) or
78
- plain text (in which case it expects to find the fields formatted in the same way as STOMP headers:
79
- seperated by line breaks, where each line consists of the field name, a colon, and the value).
80
+ Nebulous supports message bodies in either JSON or plain text (in which case it expects to find the
81
+ fields formatted in the same way as STOMP headers: seperated by line breaks, where each line
82
+ consists of the field name, a colon, and the value).
80
83
 
81
84
  There are a couple of special verbs:
82
85
 
@@ -97,12 +100,11 @@ responses to be sent.
97
100
 
98
101
  ### Responder ###
99
102
 
100
- Let's talk about the Responder use case first, since it's simpler; we've basically been talking
101
- about it for the whole of this document. You'll need to designate a queue for incoming requests on
102
- any given system. You might want more than one; in ABL, where traffic jams are likely because I
103
- can't just spawn up a thread to handle each incoming message, my current thinking is to have two
104
- incoming queues, one for reqests that take a few seconds, and another for requests that take
105
- longer.
103
+ Let's talk about the Responder use case first, since it's simpler. On any given system, you'll need
104
+ to designate a queue for incoming requests. You might want more than one. (In ABL, where traffic
105
+ jams are likely because I can't just spawn up a thread to handle each incoming message, my current
106
+ thinking is to have two incoming queues, one for reqests that take a few seconds, and another for
107
+ requests that take longer.)
106
108
 
107
109
  Remember that rule 6 says that any messages that go to a queue like this will be consumed without
108
110
  concern for whether the message will make sense to the system in question; this is basically there
@@ -126,8 +128,8 @@ really falls outside of The Protocol.
126
128
 
127
129
  In theory, Rule 3 says you can fail to use _neb-reply-to_ and pick up your response from the same
128
130
  queue you posted the message to. But rule 6 says that if you do that you don't have any guarantee
129
- at all of getting your message; the Responder will take it. So for practical purposes you almost
130
- always *have* to set a reply-to queue in your request.
131
+ at all of getting your message; the Responder will take it. So for practical purposes you *have* to
132
+ set a reply-to queue in your request.
131
133
 
132
134
  Likewise, Rule 4 says that _neb-reply-id_ is optional. But in practice you should almost certainly
133
135
  set it. Yes, you can specify a brand new queue that is unique to your request -- or probably
@@ -145,14 +147,16 @@ avoiding consuming those messages that are not.
145
147
  For a unique reply-id you could do worse than starting with the session ID that STOMP returns when
146
148
  you send a CONNECT frame; clearly the message server thinks that is unique, and it should know. In
147
149
  the Ruby Stomp gem, you can get it with `client.connection_frame().headers["session"]` where client
148
- is your `Stomp::Client` instance; in my ABL jhstomp.p library call `get_session_id()`.
150
+ is your `Stomp::Client` instance; in my ABL jhstomp.p library call `get_session_id()`. (This is how
151
+ NebulousStomp does it.)
149
152
 
150
153
  Now that you have a good reply ID you can tell which is yours by Rule 4; just test the
151
154
  _neb-in-reply-to_ header of each message.
152
155
 
153
156
  The second problem, of avoiding consuming messages that don't match your reply-id, is handled by
154
157
  careful use of STOMP. If when subscribing you set the header `ack:client-individual`, then you must
155
- manually acknowledge each message you want to consume with an ACK frame.
158
+ manually acknowledge each message you want to consume with an ACK frame. (Again, how NebulousStomp
159
+ does it.)
156
160
 
157
161
  Finally, you get to handle the response. Rule 1 says that it will either be an error verb, a
158
162
  success verb ... or something else specific to the verb. The nature of messages in responses is
@@ -163,5 +167,8 @@ enforce that.
163
167
 
164
168
  Note also that while The Protocol says that a request should always result in a response, there is
165
169
  nothing to say that the sender of the request should care -- say, in the example of a request that
166
- results in a report being emailed, which takes 20 minutes.
170
+ results in a report being emailed, which takes 20 minutes. In practice where that is the case I've
171
+ been sending the success verb early, to say "yes, got your message, I see that it is valid -- don't
172
+ wait up"; but the party that sends the message doesn't have to check it, since it's not very
173
+ helpful.
167
174
 
@@ -0,0 +1,104 @@
1
+ require 'nebulous_stomp/listener'
2
+ require 'nebulous_stomp/stomp_handler_null'
3
+
4
+ include NebulousStomp
5
+
6
+
7
+ describe Listener do
8
+
9
+ let(:target1) { Target.new(name: "foo", sendQueue: "alpha", receiveQueue: "beta") }
10
+ let(:listener1) { Listener.new(target1) }
11
+ let(:handler) { StompHandlerNull.new }
12
+
13
+
14
+ describe "#new" do
15
+
16
+ it "should accept a queue name or a Target" do
17
+ expect{ Listener.new }.to raise_error ArgumentError
18
+
19
+ expect{ Listener.new target1 }.not_to raise_error
20
+ expect{ Listener.new "alpha" }.not_to raise_error
21
+ end
22
+
23
+ end
24
+
25
+
26
+ describe "#queue" do
27
+
28
+ it "should return the queue name when object was given one" do
29
+ expect( Listener.new("beta").queue ).to eq "beta"
30
+ end
31
+
32
+ it "should return Target.receive_queue when the object was given a target" do
33
+ expect( Listener.new(target1).queue ).to eq target1.receive_queue
34
+ end
35
+
36
+ end
37
+
38
+
39
+ describe "#stomp_handler" do
40
+
41
+ it "allows you to insert a stomp_handler object for test purposes" do
42
+ expect{ listener1.stomp_handler = handler }.not_to raise_exception
43
+ expect( listener1.send :stomp_handler ).to eq handler
44
+ end
45
+
46
+ it "defaults to StompHandler if none is inserted" do
47
+ expect( listener1.send(:stomp_handler).class ).to eq StompHandler
48
+ end
49
+
50
+ end
51
+
52
+
53
+ describe "#consume_messages" do
54
+
55
+ before do
56
+ listener1.stomp_handler = handler
57
+ handler.insert_fake "foo"
58
+ handler.insert_fake "bar"
59
+ handler.insert_fake "baz"
60
+ end
61
+
62
+ it "should yield each message on the queue" do
63
+ expect{|b| listener1.consume_messages &b }.to yield_successive_args("foo", "bar", "baz")
64
+ end
65
+
66
+ end
67
+
68
+
69
+ describe "#reply" do
70
+
71
+ before do
72
+ listener1.stomp_handler = handler
73
+ end
74
+
75
+ it "takes a queue name and a Message object" do
76
+ expect{ listener1.reply }.to raise_error ArgumentError
77
+ expect{ listener1.reply("foo") }.to raise_error ArgumentError
78
+
79
+ expect{ listener1.reply("alpha", "foo") }.not_to raise_error
80
+ end
81
+
82
+ it "sends the message to its reply_to queue" do
83
+ expect( handler ).to receive(:send_message).with("beta", "bar").and_return("bar")
84
+
85
+ listener1.reply("beta", "bar")
86
+ end
87
+
88
+ end
89
+
90
+
91
+ describe "#quit" do
92
+
93
+ it "calls StompHandler.stomp_disconnect" do
94
+ listener1.stomp_handler = handler
95
+ expect( handler ).to receive(:stomp_disconnect)
96
+ listener1.quit
97
+ end
98
+
99
+
100
+ end
101
+
102
+
103
+ end
104
+