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.
- 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
|