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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b884a207187065896c92f83149c9a9701fb4b32
|
4
|
+
data.tar.gz: 18ec13584623af59f629a7c1e4ebc2d904c59cf3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25d7c1f7cda1c2a509c837b4de72a998fbca6354bf29f9ec1a9e4931df1a22458b19b9cf57a4bf7e5c61fbb1f3d22ceeb7ff66dc93f1327756c0a98e7293e93b
|
7
|
+
data.tar.gz: b9cfecbfdb21aaa42f36162b9baf57221a53706779721a584f5998a3a4f853fd0929789722b1a8ca896231b75123a43fdecfb76a051826f4fd7ff73cebe94f5b
|
data/.hgignore
CHANGED
data/.hgtags
CHANGED
data/README.md
CHANGED
@@ -1,38 +1,235 @@
|
|
1
|
-
|
1
|
+
Introduction
|
2
|
+
============
|
3
|
+
|
4
|
+
A little module that implements The Nebulous Protocol, a way of passing data over STOMP between
|
5
|
+
different systems. Specifically, it allows you to send a message, a *Request* and receive another
|
6
|
+
message in answer, a *Response*. (Which is not something STOMP does, out of the box).
|
7
|
+
|
8
|
+
This library covers two specific use cases (three if you are picky):
|
9
|
+
|
10
|
+
1) Request-Response: a program that consumes incoming messages, works out what message to send in
|
11
|
+
response, and sends it.
|
12
|
+
|
13
|
+
2) Question-Answer: a program that sends a request and then waits for a response; the other end of
|
14
|
+
the Request-Response use case. We support optional caching of responses in Redis, to speed things up
|
15
|
+
if your program is likely to make the same request repeatedly within a short time.
|
16
|
+
|
17
|
+
3) Since we are talking to Redis, we expose a basic, simple interface for you to talk to it
|
18
|
+
yourself.
|
19
|
+
|
20
|
+
|
21
|
+
Thanks
|
22
|
+
======
|
23
|
+
|
24
|
+
This code was developed, by me, during working hours at [James Hall & Co.
|
25
|
+
Ltd](https://www.jameshall.co.uk/). I'm incredibly greatful that they have permitted me to
|
26
|
+
open-source it.
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
A Quick Example
|
31
|
+
===============
|
32
|
+
|
33
|
+
Before we get too bogged down, some code.
|
34
|
+
|
35
|
+
require "nebulous_stomp"
|
36
|
+
|
37
|
+
NebulousStomp.init( my_init_hash )
|
38
|
+
NebulousStomp.add_target(:target1, my_target)
|
39
|
+
|
40
|
+
message = NebulousStomp::Message.new(verb: "ping")
|
41
|
+
request = NebulousStomp::Request.new(:target1, message)
|
42
|
+
response = request.send
|
43
|
+
|
44
|
+
This example is for the question-answer use case. `response` will contain a NebulousStomp::Message
|
45
|
+
-- unless the target fails to respond in time, in which case a NebulousStomp::MessageTimeout will
|
46
|
+
be raised.
|
47
|
+
|
48
|
+
|
49
|
+
The Protocol
|
50
|
+
============
|
51
|
+
|
52
|
+
I natter on about this in far too much detail elsewhere, but the highly condensed version is:
|
53
|
+
|
54
|
+
* Every request always gets a response; if you don't get one, then something is wrong at the other
|
55
|
+
end.
|
56
|
+
|
57
|
+
* Request-response programs consume all messages on the queue that they listen on. They place the
|
58
|
+
response on a *different* queue, the name of which is given by the request.
|
59
|
+
|
60
|
+
* Each message has a 'unique' ID; the response has the ID of the request it responds to.
|
61
|
+
|
62
|
+
* Messages can "follow the protocol" by being in the form: verb, parameters, descripton. Requests
|
63
|
+
*must* be in this form; responses don't have to be.
|
64
|
+
|
65
|
+
* The special verb "success" in a response means "I got the message, everything is fine, nothing to
|
66
|
+
report here".
|
67
|
+
|
68
|
+
* The special verb "error" in a response means something went wrong. The description should say
|
69
|
+
what.
|
70
|
+
|
71
|
+
|
72
|
+
Targets
|
73
|
+
=======
|
74
|
+
|
75
|
+
When you have a system running a request-response loop, then the simplest way to proceed is to
|
76
|
+
assign it a pair of queues on your Stomp server: one for incoming requests, and one for it to post
|
77
|
+
responses to.
|
78
|
+
|
79
|
+
We call such a system a Target. Any other, question-answer, system (which wants to throw Requests at
|
80
|
+
that target and get a response) will need to know what those queues are; so we configure a list of
|
81
|
+
targets at startup.
|
82
|
+
|
83
|
+
Note that it is perfectly okay for a target to use more than one request queue (desirable, even,
|
84
|
+
if some requests will take time to fulfil). But we don't directly support that in Nebulous: a
|
85
|
+
Target is always one request queue, one response queue. In this case, the simplest way forward is
|
86
|
+
to define two targets.
|
87
|
+
|
88
|
+
|
89
|
+
Examples
|
2
90
|
========
|
3
91
|
|
4
|
-
|
5
|
-
|
6
|
-
|
92
|
+
Request-Response
|
93
|
+
----------------
|
94
|
+
|
95
|
+
This revisits the example from the start, but with more detail. For completeness, we configure a
|
96
|
+
Redis server for caching respsonses (which is optional) and show all the config hashes (which
|
97
|
+
certainly want to come from a config file in practice).
|
98
|
+
|
99
|
+
require "nebulous_stomp"
|
100
|
+
|
101
|
+
host = {login: "guest", passcode: "guest", host: "10.11.12.13", ssl: false}
|
102
|
+
stomp = {hosts: [host], reliable: false}
|
103
|
+
redis = {host: '127.0.0.1', port: 6379, db: 0}
|
104
|
+
|
105
|
+
config = { stompConnectHash: stomp,
|
106
|
+
redisCOnnectHash: redis,
|
107
|
+
messageTimeout: 5,
|
108
|
+
cacheTimeout: 30 }
|
109
|
+
|
110
|
+
target = { sendQueue: "/queue/in",
|
111
|
+
receiveQueue: "/queue/out",
|
112
|
+
messageTimeout: 7 }
|
113
|
+
|
114
|
+
NebulousStomp.init(config)
|
115
|
+
NebulousStomp.add_target(:target1, target)
|
116
|
+
|
117
|
+
message = NebulousStomp::Message.new(verb: "ping")
|
118
|
+
request = NebulousStomp::Request.new(:target1, message)
|
119
|
+
|
120
|
+
response1 = request.send
|
121
|
+
response2 = request.send
|
122
|
+
|
123
|
+
`response1` will be filled from the target; `response2` will be filled from the Redis cache
|
124
|
+
(provided that line gets called within 30 seconds of the previous line). (Obviously this is
|
125
|
+
pointless and for example only.)
|
126
|
+
|
127
|
+
The stomp hash is passed unchanged to the Stomp gem; the redis hash is passed unchanged to the
|
128
|
+
Redis gem. See these gems for details about what they should contain.
|
129
|
+
|
130
|
+
Message.new takes a single hash as an argument; it accepts a long list of possible keys, but mostly
|
131
|
+
I imagine you will be using 'verb', 'params', and 'desc'. It's worth also noting 'replyTo', which
|
132
|
+
sets the queue to reply to; if missing then Request sets it from the Target, of course.
|
133
|
+
|
134
|
+
This rather specific example contains three seperate timeout values. The message timeout is the
|
135
|
+
time we wait for a response before raising MessageTimeout. The value in the config hash is a
|
136
|
+
default; in this example it is overidden on the target. The cache timeout is, of course, the time
|
137
|
+
that the response is kept on the cache. These values can be further overridden for specific
|
138
|
+
messages.
|
139
|
+
|
140
|
+
Often even with a cache set up, you don't want to use it (for requests that trigger database
|
141
|
+
updates, for example); in which case the method to call is `send_no_cache`.
|
142
|
+
|
143
|
+
Question-Answer
|
144
|
+
---------------
|
145
|
+
|
146
|
+
require "nebulous_stomp"
|
147
|
+
|
148
|
+
NebulousStomp.init(config)
|
149
|
+
target = NebulousStomp.add_target(:target1, target)
|
150
|
+
|
151
|
+
listener = NebulousStomp::Listener.new(target)
|
152
|
+
|
153
|
+
listener.consume_messages do |msg|
|
154
|
+
begin
|
155
|
+
|
156
|
+
case msg.verb
|
157
|
+
when "ping"
|
158
|
+
listener.reply *msg.respond_with_success
|
159
|
+
when "time"
|
160
|
+
listener.reply *msg.respond_with_protocol("timeresponse", Time.now)
|
161
|
+
else
|
162
|
+
listener.reply *msg.respond_with_error("Bad verb #{msg.verb}")
|
163
|
+
end
|
164
|
+
|
165
|
+
rescue
|
166
|
+
listener.reply *msg.respond_with_error($!)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
loop { sleep 5 }
|
171
|
+
|
172
|
+
This example implements a target that responds to two verbs. In responce to "ping" it sends a
|
173
|
+
success verb, indicating it got the message. In response to "time" it sends a "timeresponse" verb
|
174
|
+
with the current time as a parameter. For any other verb on its receive queue, it responds with an
|
175
|
+
error verb.
|
176
|
+
|
177
|
+
`Listener.new` requires either a Target object or a queue name. This is different from Request,
|
178
|
+
which can take a target name. You can always retreive a Target object yourself, though, like this:
|
179
|
+
|
180
|
+
target = NebulousParam.get_target(targetname)
|
181
|
+
|
182
|
+
If you want to respond with a message that does not follow the verb-parameter-description part of
|
183
|
+
the protocol, then you can pass an arbitrary message body to `msg.respond()`.
|
184
|
+
|
185
|
+
Note the error handling. This is especially important because the body that you pass to the
|
186
|
+
`consume_messages` method is actually being run in a thread, by the Stomp gem; by default all
|
187
|
+
errors will be swallowed silently. As you can see, `message.respond_with_error` can take an
|
188
|
+
exception as a parameter.
|
189
|
+
|
190
|
+
Note also, for the same reason, that your program must hold the main thread open while
|
191
|
+
`consume_messages` is running; if the main thread ends, the program stops.
|
192
|
+
|
193
|
+
Redis
|
194
|
+
-----
|
195
|
+
|
196
|
+
require "nebulous_stomp/redis_helper"
|
197
|
+
|
198
|
+
# ...parameters get set here...
|
199
|
+
|
200
|
+
redis = NebulousStomp::RedisHelper.new
|
201
|
+
|
202
|
+
redis.set(:thing, "thingy")
|
203
|
+
redes.set(:gone_in_30_seconds, "thingy", 30)
|
204
|
+
|
205
|
+
value = redis.get(:thing)
|
206
|
+
|
207
|
+
redis.del(:thing)
|
7
208
|
|
8
|
-
|
209
|
+
Obviously this is not so much an example as it is some random calls to RedisHelper. But hopefully
|
210
|
+
it is fairly self-explanatory.
|
9
211
|
|
10
|
-
First, sending a request for information and waiting for a response, which
|
11
|
-
might come from a cache of previous responses, if you allow it. To do
|
12
|
-
this you should create a Nebulous::NebRequest, which will return a
|
13
|
-
Nebulous::Message.
|
14
212
|
|
15
|
-
|
16
|
-
|
17
|
-
class, which will again furnish Nebulous::Meessage objects, and allow you to
|
18
|
-
create them.
|
213
|
+
A list of classes
|
214
|
+
=================
|
19
215
|
|
20
|
-
|
21
|
-
Nebulous.add_logger.
|
216
|
+
To help you drill down to the API documentation. These are the externally-facing classes:
|
22
217
|
|
23
|
-
|
24
|
-
|
25
|
-
|
218
|
+
* Listener -- implements the request-response use case
|
219
|
+
* Message -- a Nebulous message
|
220
|
+
* NebulousStomp -- main class
|
221
|
+
* RedisHelper -- implements the Redis use case
|
222
|
+
* Request -- implements the Request-Response use case; a wrapper for Message
|
223
|
+
* Target -- represents a single Target
|
224
|
+
|
225
|
+
These classes are used internally:
|
26
226
|
|
27
|
-
|
227
|
+
* Param -- helper class to store and return configuration
|
228
|
+
* RedisHandler -- internal class to wrap the Redis gem
|
229
|
+
* RedisHandlerNull -- a "mock" version of RedisHandler for use in testing
|
230
|
+
* StompHandler -- internal class to wrap the Stomp gem
|
231
|
+
* StompHandlerNull -- a "mock" version of StompHandler for use in testing
|
28
232
|
|
29
|
-
|
30
|
-
|
31
|
-
* Nebulous::NebRequest
|
32
|
-
* Nebulous::NebRequestNull
|
33
|
-
* Nebulous::Message
|
34
|
-
* Nebulous::StompHandler
|
35
|
-
* Nebulous::StompHandlerNull
|
36
|
-
* Nebulous::RedisHandler
|
37
|
-
* Nebulous::RedisHandlerNull
|
233
|
+
You might find the null classes useful in your own tests; both Listener and Request allow the
|
234
|
+
injection of mock handler objects. You must require them seperately, though.
|
38
235
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
|
3
|
+
:init:
|
4
|
+
:stompConnectHash:
|
5
|
+
hosts:
|
6
|
+
- login: guest
|
7
|
+
passcode: guest
|
8
|
+
host: '10.11.12.13'
|
9
|
+
port: 61613
|
10
|
+
ssl: false
|
11
|
+
reliable: false
|
12
|
+
|
13
|
+
:redisConnectHash:
|
14
|
+
host: '127.0.0.1'
|
15
|
+
port: 6379
|
16
|
+
db : 0
|
17
|
+
|
18
|
+
:messageTimeout: 2
|
19
|
+
:cacheTimeout : 10
|
20
|
+
|
21
|
+
:target:
|
22
|
+
:sendQueue: '/queue/featuretestsend'
|
23
|
+
:receiveQueue: '/queue/featuretestreceive'
|
24
|
+
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'nebulous_stomp'
|
2
|
+
require 'nebulous_stomp/redis_helper'
|
3
|
+
require 'nebulous_stomp/redis_handler'
|
4
|
+
|
5
|
+
require_relative 'gimme'
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
# These are the feature tests for Nebulous. They are not run when you type `rspec`; that only gets
|
10
|
+
# you the unit tests. You have to name this directory to run it: `rspec feature`.
|
11
|
+
#
|
12
|
+
# These tests require an actual, working STOMP server and an actual, working Redis server (and ones
|
13
|
+
# which you don't mind sending test messages to, at that). You should configure connection to this
|
14
|
+
# in features/connection.yaml; an example file is provided, features/connection_example.yaml.
|
15
|
+
#
|
16
|
+
describe 'stomp use cases:' do
|
17
|
+
|
18
|
+
def init_nebulous(configfile)
|
19
|
+
config = YAML.load(File.open configfile)
|
20
|
+
NebulousStomp.init config[:init]
|
21
|
+
NebulousStomp.add_target("featuretest", config[:target] )
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_request(verb)
|
25
|
+
message = NebulousStomp::Message.new(verb: verb)
|
26
|
+
NebulousStomp::Request.new("featuretest", message)
|
27
|
+
end
|
28
|
+
|
29
|
+
before(:all) do
|
30
|
+
init_nebulous 'feature/connection.yaml'
|
31
|
+
Thread.new{ Gimme.new("feature/connection.yaml").run; sleep 15 }
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:hash) do
|
35
|
+
{ "verb" => "foo",
|
36
|
+
"parameters" => "bar",
|
37
|
+
"description" => "baz" }
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:redis) { NebulousStomp::RedisHelper.new }
|
42
|
+
|
43
|
+
# Plain redis access for debugging
|
44
|
+
def redis_backdoor(cmd, arg)
|
45
|
+
hash = NebulousStomp::Param.get :redisConnectHash
|
46
|
+
handler = NebulousStomp::RedisHandler.new hash
|
47
|
+
handler.connect unless handler.connected?
|
48
|
+
handler.send(cmd.to_sym, arg.to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Plain stomp access for debugging
|
52
|
+
def stomp_backdoor(cmd, queue, msg=nil)
|
53
|
+
hash = NebulousStomp::Param.get :stompConnectHash
|
54
|
+
handler = NebulousStomp::StompHandler.new hash
|
55
|
+
|
56
|
+
case cmd
|
57
|
+
when :send
|
58
|
+
handler.send_message(queue, msg)
|
59
|
+
return true
|
60
|
+
when :listen
|
61
|
+
messages = []
|
62
|
+
handler.listen_with_timeout(queue, 1) {|msg| messages << msg; false } rescue nil
|
63
|
+
return messages, handler.connected?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
##
|
69
|
+
# tests for the request-response use case - a server that consumes messages and responds with
|
70
|
+
# other messages.
|
71
|
+
#
|
72
|
+
# Note that it's the Gimme class, in the thread above, that is actually doing the responding; we
|
73
|
+
# just send a message to it and check the response.
|
74
|
+
#
|
75
|
+
describe "request-response:" do
|
76
|
+
|
77
|
+
it "can respond to a message with a success verb" do
|
78
|
+
response = new_request("gimmesuccess").send_no_cache
|
79
|
+
|
80
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
81
|
+
expect( response.verb ).to eq "success"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "can respond to a message with an error verb" do
|
85
|
+
response = new_request("gimmeerror").send_no_cache
|
86
|
+
|
87
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
88
|
+
expect( response.verb ).to eq "error"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "can respond to a message with a specific Protocol message" do
|
92
|
+
response = new_request("gimmeprotocol").send_no_cache
|
93
|
+
|
94
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
95
|
+
expect( response.verb ).to eq hash["verb"]
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can respond to a message with a non-Protocol message" do
|
99
|
+
message = NebulousStomp::Message.new(verb: 'gimmemessage', contentType: 'text')
|
100
|
+
response = NebulousStomp::Request.new("featuretest", message).send_no_cache
|
101
|
+
|
102
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
103
|
+
expect( response.verb ).to be_nil
|
104
|
+
expect( response.body ).to eq "weird message body"
|
105
|
+
end
|
106
|
+
|
107
|
+
it "can send a message >32k" do
|
108
|
+
message = NebulousStomp::Message.new( verb: 'gimmebigmessage',
|
109
|
+
params: "32",
|
110
|
+
contentType: 'text' )
|
111
|
+
|
112
|
+
response = NebulousStomp::Request.new("featuretest", message).send_no_cache
|
113
|
+
|
114
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
115
|
+
expect( response.body.size ).to be > (1024 * 32)
|
116
|
+
expect( response.body[0..2] ).to eq "foo"
|
117
|
+
expect( response.body[-3..-1] ).to eq "bar"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "can send a message >128k" do
|
121
|
+
message = NebulousStomp::Message.new( verb: 'gimmebigmessage',
|
122
|
+
params: "128",
|
123
|
+
contentType: 'text' )
|
124
|
+
|
125
|
+
response = NebulousStomp::Request.new("featuretest", message).send_no_cache
|
126
|
+
|
127
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
128
|
+
expect( response.body.size ).to be > (1024 * 128)
|
129
|
+
expect( response.body[0..2] ).to eq "foo"
|
130
|
+
expect( response.body[-3..-1] ).to eq "bar"
|
131
|
+
end
|
132
|
+
|
133
|
+
it "can send a message >512k" do
|
134
|
+
message = NebulousStomp::Message.new( verb: 'gimmebigmessage',
|
135
|
+
params: "512",
|
136
|
+
contentType: 'text' )
|
137
|
+
|
138
|
+
response = NebulousStomp::Request.new("featuretest", message).send_no_cache
|
139
|
+
|
140
|
+
expect( response ).to be_kind_of(NebulousStomp::Message)
|
141
|
+
expect( response.body.size ).to be > (1024 * 512)
|
142
|
+
expect( response.body[0..2] ).to eq "foo"
|
143
|
+
expect( response.body[-3..-1] ).to eq "bar"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
##
|
148
|
+
|
149
|
+
|
150
|
+
##
|
151
|
+
# Tests for the question-and-answer use case -- a process that sends a request to a
|
152
|
+
# request-response server and waits for an answering response
|
153
|
+
#
|
154
|
+
describe "question-and-answer:" do
|
155
|
+
|
156
|
+
|
157
|
+
it "can send a JSON message and get a JSON response" do
|
158
|
+
message = NebulousStomp::Message.new(verb: 'gimmeprotocol', contentType: 'application/json')
|
159
|
+
response = NebulousStomp::Request.new("featuretest", message).send_no_cache
|
160
|
+
|
161
|
+
expect( response.content_type ).to eq 'application/json'
|
162
|
+
expect( response.body ).to eq hash
|
163
|
+
expect( response.stomp_body ).to eq hash.to_json
|
164
|
+
end
|
165
|
+
|
166
|
+
it "can send a text message and get a text response" do
|
167
|
+
message = NebulousStomp::Message.new(verb: 'gimmeprotocol', contentType: 'application/text')
|
168
|
+
response = NebulousStomp::Request.new("featuretest", message).send_no_cache
|
169
|
+
|
170
|
+
expect( response.content_type ).to eq 'application/text'
|
171
|
+
expect( response.body ).to eq hash
|
172
|
+
|
173
|
+
hash.each do |k,v|
|
174
|
+
expect( response.stomp_body ).to match(/#{k}: *#{v}/)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it "can cache a response in Redis" do
|
179
|
+
message = NebulousStomp::Message.new(verb: 'gimmeprotocol', contentType: 'application/text')
|
180
|
+
request = NebulousStomp::Request.new("featuretest", message)
|
181
|
+
signature = {verb:"gimmeprotocol"}.to_json
|
182
|
+
|
183
|
+
redis_backdoor(:del, signature)
|
184
|
+
request.send
|
185
|
+
expect( redis_backdoor(:get, signature) ).not_to be_nil
|
186
|
+
end
|
187
|
+
|
188
|
+
it "will receive its response without disturbing any others" do
|
189
|
+
msg1 = NebulousStomp::Message.new(verb: 'backdoor', contentType: 'application/text')
|
190
|
+
msg2 = NebulousStomp::Message.new(verb: 'gimmeprotocol', contentType: 'application/text')
|
191
|
+
target = NebulousStomp.get_target(:featuretest)
|
192
|
+
|
193
|
+
# Place msg1 on the queue directly
|
194
|
+
stomp_backdoor(:send, target.send_queue, msg1)
|
195
|
+
|
196
|
+
# Send Msg2 to the target, so that Gimme puts the response on the queue and then we read it
|
197
|
+
response2 = NebulousStomp::Request.new(target, msg2).send_no_cache
|
198
|
+
|
199
|
+
# Now read the messages left on the queue
|
200
|
+
leftovers, connected = stomp_backdoor(:listen, target.send_queue)
|
201
|
+
|
202
|
+
expect( response2 ).to be_kind_of NebulousStomp::Message
|
203
|
+
expect( response2.verb ).to eq "foo"
|
204
|
+
expect( leftovers.map(&:verb) ).to include("backdoor")
|
205
|
+
expect( leftovers.map(&:verb) ).not_to include("foo")
|
206
|
+
expect( connected ).to be_truthy
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
##
|
211
|
+
|
212
|
+
|
213
|
+
##
|
214
|
+
# Tests for the Redis use case -- user wants to access Redis so we grant them access through our
|
215
|
+
# connection to it.
|
216
|
+
#
|
217
|
+
describe "redis:" do
|
218
|
+
|
219
|
+
it "can set a value in the store" do
|
220
|
+
redis.del(:foo) rescue nil
|
221
|
+
redis.set(:foo, "bar")
|
222
|
+
expect( redis.get(:foo) ).to eq "bar"
|
223
|
+
end
|
224
|
+
|
225
|
+
it "can set a value in the store with a timeout" do
|
226
|
+
redis.set(:foo, "bar", 1)
|
227
|
+
expect( redis.get :foo ).to eq "bar"
|
228
|
+
sleep 2
|
229
|
+
expect( redis.get :foo ).to be_nil
|
230
|
+
end
|
231
|
+
|
232
|
+
it "can get a value from the store" do
|
233
|
+
redis.set(:foo, bar: "baz")
|
234
|
+
expect( redis.get(:foo) ).to eq( {bar: "baz"} )
|
235
|
+
end
|
236
|
+
|
237
|
+
it "can remove a value from the store" do
|
238
|
+
redis.set(:foo, "bar")
|
239
|
+
redis.del(:foo)
|
240
|
+
expect( redis.get :foo ).to be_nil
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
##
|
245
|
+
|
246
|
+
end
|
247
|
+
|
data/feature/gimme.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
$: << "./lib" if __FILE__ == $0
|
2
|
+
|
3
|
+
require 'nebulous_stomp'
|
4
|
+
require 'yaml'
|
5
|
+
require "pry"
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
# A little request-reponse server for the feature test
|
10
|
+
#
|
11
|
+
class Gimme
|
12
|
+
|
13
|
+
def initialize(configfile)
|
14
|
+
@config = load_config configfile
|
15
|
+
@target = init_nebulous
|
16
|
+
#@listener = NebulousStomp::Listener.new(@target)
|
17
|
+
@listener = NebulousStomp::Listener.new("/queue/featuretestreceive")
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
@listener.consume_messages{|msg| reply msg}
|
22
|
+
end
|
23
|
+
|
24
|
+
def quit
|
25
|
+
@listener.quit
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def load_config(file)
|
31
|
+
YAML.load(File.open file)
|
32
|
+
end
|
33
|
+
|
34
|
+
def init_nebulous
|
35
|
+
NebulousStomp.init @config[:init]
|
36
|
+
NebulousStomp.add_target("featuretest", @config[:target] )
|
37
|
+
end
|
38
|
+
|
39
|
+
def reply(msg)
|
40
|
+
queue, message =
|
41
|
+
case msg.verb
|
42
|
+
when "gimmesuccess"
|
43
|
+
msg.respond_with_success
|
44
|
+
|
45
|
+
when "gimmeerror"
|
46
|
+
msg.respond_with_error("the error you wanted")
|
47
|
+
|
48
|
+
when "gimmeprotocol"
|
49
|
+
msg.respond_with_protocol("foo", "bar", "baz")
|
50
|
+
|
51
|
+
when "gimmemessage"
|
52
|
+
msg.respond("weird message body")
|
53
|
+
|
54
|
+
when "gimmebigmessage"
|
55
|
+
body = big_body msg.params
|
56
|
+
msg.respond body
|
57
|
+
|
58
|
+
else fail "unknown verb #{msg.verb} in Gimme"
|
59
|
+
end
|
60
|
+
|
61
|
+
@listener.reply(queue, message)
|
62
|
+
|
63
|
+
rescue
|
64
|
+
puts "ERROR: #{$!}"
|
65
|
+
$!.backtrace.each{|e| puts e }
|
66
|
+
end
|
67
|
+
|
68
|
+
def big_body(params)
|
69
|
+
kb = params.to_f; fail "not a size" if kb == 0
|
70
|
+
|
71
|
+
body = "foo"
|
72
|
+
body << "Q" * (1024 * kb)
|
73
|
+
body << "bar"
|
74
|
+
|
75
|
+
body
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
if __FILE__ == $0
|
82
|
+
|
83
|
+
begin
|
84
|
+
g = Gimme.new('./feature/connection.yaml')
|
85
|
+
g.run
|
86
|
+
loop { sleep 5 }
|
87
|
+
ensure
|
88
|
+
g.quit
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|