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.
- checksums.yaml +4 -4
- data/.hgignore +2 -0
- data/.hgtags +1 -0
- data/README.md +225 -28
- data/feature/connection_example.yaml +24 -0
- data/feature/feature_test_spec.rb +247 -0
- data/feature/gimme.rb +91 -0
- data/lib/nebulous_stomp/listener.rb +107 -0
- data/lib/nebulous_stomp/message.rb +132 -265
- data/lib/nebulous_stomp/msg/body.rb +169 -0
- data/lib/nebulous_stomp/msg/header.rb +98 -0
- data/lib/nebulous_stomp/param.rb +16 -35
- data/lib/nebulous_stomp/redis_handler.rb +19 -29
- data/lib/nebulous_stomp/redis_handler_null.rb +12 -11
- data/lib/nebulous_stomp/redis_helper.rb +110 -0
- data/lib/nebulous_stomp/request.rb +212 -0
- data/lib/nebulous_stomp/stomp_handler.rb +30 -96
- data/lib/nebulous_stomp/stomp_handler_null.rb +8 -22
- data/lib/nebulous_stomp/target.rb +52 -0
- data/lib/nebulous_stomp/version.rb +1 -1
- data/lib/nebulous_stomp.rb +63 -50
- data/md/LICENSE.txt +20 -2
- data/md/nebulous_protocol.md +25 -18
- data/spec/listener_spec.rb +104 -0
- data/spec/message_spec.rb +227 -116
- data/spec/nebulous_spec.rb +44 -9
- data/spec/param_spec.rb +16 -33
- data/spec/redis_handler_null_spec.rb +0 -2
- data/spec/redis_handler_spec.rb +0 -2
- data/spec/redis_helper_spec.rb +107 -0
- data/spec/request_spec.rb +249 -0
- data/spec/stomp_handler_null_spec.rb +33 -34
- data/spec/stomp_handler_spec.rb +1 -74
- data/spec/target_spec.rb +97 -0
- metadata +20 -11
- data/lib/nebulous_stomp/nebrequest.rb +0 -259
- data/lib/nebulous_stomp/nebrequest_null.rb +0 -37
- data/spec/nebrequest_null_spec.rb +0 -219
- data/spec/nebrequest_spec.rb +0 -239
- data/spec/through_test_spec.rb +0 -80
data/lib/nebulous_stomp.rb
CHANGED
@@ -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/
|
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
|
-
#
|
16
|
-
#
|
17
|
-
# Redis.
|
17
|
+
# NebulousStomp
|
18
|
+
# =============
|
18
19
|
#
|
19
|
-
#
|
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
|
-
#
|
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
|
-
#
|
27
|
-
#
|
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
|
-
#
|
32
|
-
#
|
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
|
35
|
-
#
|
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
|
-
#
|
37
|
+
# These are the externally-facing classes:
|
39
38
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
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
|
-
#
|
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
|
-
|
76
|
+
nil
|
75
77
|
end
|
76
78
|
|
77
|
-
|
79
|
+
##
|
78
80
|
# :call-seq:
|
79
|
-
#
|
81
|
+
# NebulousStomp.add_target(name, targetHash) -> Target
|
82
|
+
#
|
83
|
+
# Add a Nebulous target called <name> with a details as per <targetHash>.
|
80
84
|
#
|
81
|
-
#
|
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
|
-
#
|
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)
|
88
|
-
|
89
|
-
|
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
|
-
#
|
118
|
+
# NebulousStomp.logger.info(__FILE__) { "message" }
|
103
119
|
#
|
104
|
-
# Return a Logger instance to log things to.
|
105
|
-
#
|
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
|
-
|
1
|
+
The MIT License (MIT)
|
2
2
|
|
3
|
-
|
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.
|
data/md/nebulous_protocol.md
CHANGED
@@ -6,8 +6,10 @@ Nebulous: The Protocol
|
|
6
6
|
|
7
7
|
Introduction
|
8
8
|
------------
|
9
|
-
|
10
|
-
|
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
|
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
|
78
|
-
|
79
|
-
|
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
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
130
|
-
|
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
|
+
|