nebulous_stomp 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.hgignore +19 -0
- data/.hgtags +16 -0
- data/.rspec +8 -0
- data/.travis.yml +3 -0
- data/.yardoc/checksums +10 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/Gemfile +4 -0
- data/Guardfile +17 -0
- data/README.md +38 -0
- data/Rakefile +31 -0
- data/lib/nebulous/message.rb +368 -0
- data/lib/nebulous/nebrequest.rb +254 -0
- data/lib/nebulous/nebrequest_null.rb +39 -0
- data/lib/nebulous/param.rb +139 -0
- data/lib/nebulous/redis_handler.rb +103 -0
- data/lib/nebulous/redis_handler_null.rb +61 -0
- data/lib/nebulous/stomp_handler.rb +290 -0
- data/lib/nebulous/stomp_handler_null.rb +98 -0
- data/lib/nebulous/version.rb +5 -0
- data/lib/nebulous.rb +140 -0
- data/md/LICENSE.txt +3 -0
- data/md/nebulous_protocol.md +193 -0
- data/nebulous.gemspec +46 -0
- data/spec/doc_no_pending.rb +5 -0
- data/spec/helpers.rb +22 -0
- data/spec/message_spec.rb +575 -0
- data/spec/nebrequest_null_spec.rb +223 -0
- data/spec/nebrequest_spec.rb +241 -0
- data/spec/nebulous_spec.rb +124 -0
- data/spec/param_spec.rb +146 -0
- data/spec/redis_handler_null_spec.rb +97 -0
- data/spec/redis_handler_spec.rb +141 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/spec_helper_old.rb +19 -0
- data/spec/stomp_handler_null_spec.rb +173 -0
- data/spec/stomp_handler_spec.rb +446 -0
- data/tags +134 -0
- metadata +245 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
Nebulous: The Protocol
|
2
|
+
======================
|
3
|
+
|
4
|
+
(It was going to be "The Nebulous Protocol", but that sounds like something with
|
5
|
+
Matt Damon in it.)
|
6
|
+
|
7
|
+
|
8
|
+
Introduction
|
9
|
+
------------
|
10
|
+
Project Nebulous is a STOMP client written in ABL, and also the protocol that
|
11
|
+
allows our different systems, ABL or not, to pass messages. This document,
|
12
|
+
obviously, concentrates on the protocol.
|
13
|
+
|
14
|
+
The basic idea is this: one system passes a message, a _request_; and then
|
15
|
+
waits for an answer to that message, a _response_, from another system. STOMP
|
16
|
+
doesn't explicitly support that; the protocol does it.
|
17
|
+
|
18
|
+
(Note that a request and a response are just notional terms; you might in
|
19
|
+
theory have a request which provokes a response which provokes another response
|
20
|
+
in return, in which case the second message is both a response and a request,
|
21
|
+
and the terms get rather less useful.)
|
22
|
+
|
23
|
+
|
24
|
+
The Protocol
|
25
|
+
------------
|
26
|
+
Let's start with the actual protocol, the rules. They won't make sense without
|
27
|
+
reading the rest of the document, but:
|
28
|
+
|
29
|
+
1. Every valid request (that is, every message with an identifiable verb other
|
30
|
+
than "success" or "error") will always generate a response. If the handling
|
31
|
+
routine cannot generate a valid response, or if there is no routine to handle
|
32
|
+
the verb in question, then the response should be the error verb. If no
|
33
|
+
response is required, you should still receive the success verb.
|
34
|
+
|
35
|
+
2. You should expect that requests that do not have a verb will not be responded
|
36
|
+
to or even understood. (However, there is no general requirement for message
|
37
|
+
bodies to follow the verb / parameters / description format. If you have a
|
38
|
+
program that sends a request and waits for a response, the format of that
|
39
|
+
response is outside the remit of The Protocol.)
|
40
|
+
|
41
|
+
3. If a request has the _neb-reply-to_ header, the response should use that as
|
42
|
+
a destination; otherwise it should be sent to the same destination that the
|
43
|
+
request was sent to.
|
44
|
+
|
45
|
+
4. If the request has a _neb-reply-id_ header, then the response should set the
|
46
|
+
_neb-in-reply-to_ header to that.
|
47
|
+
|
48
|
+
5. The request may specify the content type of a response (JSON or text) by
|
49
|
+
it's own content type. That is, if a response can be in either form, it
|
50
|
+
should take the form of the request as an indication as which to use.
|
51
|
+
|
52
|
+
6. A given queue that is the target of requests within The Protocol will be for
|
53
|
+
only one system or common group of systems. They may consume (ACK) messages
|
54
|
+
sent to it even if they do not have the facility to process them. If
|
55
|
+
multiple systems share a queue, they should understand that messages will be
|
56
|
+
consumed from it at random.
|
57
|
+
|
58
|
+
|
59
|
+
Components
|
60
|
+
----------
|
61
|
+
By way of an explanation of the above section.
|
62
|
+
|
63
|
+
### Headers ###
|
64
|
+
|
65
|
+
STOMP allows you to define custom headers. We have three.
|
66
|
+
|
67
|
+
* _neb-reply-to_ is may be set on a request to the destination that any
|
68
|
+
response should be posted to.
|
69
|
+
|
70
|
+
* _neb-reply-id_ may be set on a request to an arbitrary string to help
|
71
|
+
you identify the response.
|
72
|
+
|
73
|
+
* _neb-in-reply-to_ is set on a response and contains the nebulous-reply-id
|
74
|
+
of the request.
|
75
|
+
|
76
|
+
### Message Body ###
|
77
|
+
|
78
|
+
The Protocol specifies a format for the message body. It consists of three
|
79
|
+
fields:
|
80
|
+
|
81
|
+
* _verb_ is the keyword that tells the receiving system how to process the
|
82
|
+
message.
|
83
|
+
|
84
|
+
* _parameters_ is an arbitrary field that is context-dependant on verb. The
|
85
|
+
routine that handles the verb is expected to be able to parse it. It is
|
86
|
+
optional.
|
87
|
+
|
88
|
+
* _description_ is a text field and can contain anything. It is optional.
|
89
|
+
|
90
|
+
Nebulous supports message bodies in either JSON (in which case parameters is an
|
91
|
+
array) or plain text (in which case it expects to find the fields formatted in
|
92
|
+
the same way as STOMP headers: seperated by line breaks, where each line
|
93
|
+
consists of the field name, a colon, and the value).
|
94
|
+
|
95
|
+
There are a couple of special verbs:
|
96
|
+
|
97
|
+
* _success_ as a verb in a response is used when no information needs to be
|
98
|
+
returned except that the request operation went ahead without problem.
|
99
|
+
|
100
|
+
* _error_ as a verb is used when the requested operation failed. The
|
101
|
+
expectation is that you will put an error message in the description field;
|
102
|
+
you can use parameters as you see fit, of course.
|
103
|
+
|
104
|
+
|
105
|
+
Notes on usage
|
106
|
+
--------------
|
107
|
+
There are two use cases here. The first (let's call it _Q & A_) is when a
|
108
|
+
process needs information and sends a request, then waits for a response. The
|
109
|
+
second (let's call it _Responder_) is at the other end of that process; it
|
110
|
+
camps onto one or more queues waiting for requests, and arranges for responses
|
111
|
+
to be sent.
|
112
|
+
|
113
|
+
### Responder ###
|
114
|
+
|
115
|
+
Let's talk about the Responder use case first, since it's simpler; we've
|
116
|
+
basically been talking about it for the whole of this document. You'll need to
|
117
|
+
designate a queue for incoming requests on any given system. You might want
|
118
|
+
more than one; in ABL, where traffic jams are likely because I can't just
|
119
|
+
spawn up a thread to handle each incoming message, my current thinking is to
|
120
|
+
have two incoming queues, one for reqests that take a few seconds, and another
|
121
|
+
for requests that take longer.
|
122
|
+
|
123
|
+
Remember that rule 6 says that any messages that go to a queue like this will
|
124
|
+
be consumed without concern for whether the message will make sense to the
|
125
|
+
system in question; this is basically there so that the ABL code can split up
|
126
|
+
the process of grabbing new messages from the process of working out what they
|
127
|
+
are and how to answer them.
|
128
|
+
|
129
|
+
But the simplicity is appealing, regardless: post to this queue and the
|
130
|
+
Responder that looks after this system will process it. Rule 1 guarantees
|
131
|
+
you an answer so long as the system is up, so if you don't get one then either
|
132
|
+
the target system is down or it's too busy to respond.
|
133
|
+
|
134
|
+
The expectation is that the Responder system should use the verb of the message
|
135
|
+
to control how it is dealt with. The parameters field is verb-specific; the
|
136
|
+
combination of verb, expected parameters, and the nature of the returned
|
137
|
+
message form a sort of contract, ie "verb x always expects parameters like
|
138
|
+
this, and always behaves like that in response". This is partly implied by Rule
|
139
|
+
2, I think.
|
140
|
+
|
141
|
+
### Q & A ###
|
142
|
+
|
143
|
+
The Q & A use case is more interesting since it's what the whole thing is for,
|
144
|
+
but some of it really falls outside of The Protocol.
|
145
|
+
|
146
|
+
In theory, Rule 3 says you can fail to use _neb-reply-to_ and pick up your
|
147
|
+
response from the same queue you posted the message to. But rule 6 says that if
|
148
|
+
you do that you don't have any guarantee at all of getting your message; the
|
149
|
+
Responder will take it. So for practical purposes you almost always *have* to
|
150
|
+
set a reply-to queue in your request.
|
151
|
+
|
152
|
+
Likewise, Rule 4 says that _neb-reply-id_ is optional. But in practice you
|
153
|
+
should almost certainly set it. Yes, you can specify a brand new queue that is
|
154
|
+
unique to your request -- or probably unique, anyway -- but it turns out that
|
155
|
+
some message servers, our RabbitMQ included, don't let you subscribe to a queue
|
156
|
+
that doesn't exist. It's easy enough to create a queue: you just send a message
|
157
|
+
to it. But now there are two messages in that queue (or more if turns out that
|
158
|
+
you've got some queue namespace collision after all) and you have to pick your
|
159
|
+
response from it, and the easiest way is to set a reply id.
|
160
|
+
|
161
|
+
So let's assume a median worst case: you're posting a request to a Responder
|
162
|
+
queue with a reply-id set to something hopefully unique, and reply-to set to a
|
163
|
+
common queue that many processes use to pick up replies. There are two
|
164
|
+
challenges here: first, working out which message is yours; second, avoiding
|
165
|
+
consuming those messages that are not.
|
166
|
+
|
167
|
+
For a unique reply-id you could do worse than starting with the session ID that
|
168
|
+
STOMP returns when you send a CONNECT frame; clearly the message server thinks
|
169
|
+
that is unique, and it should know. In the Ruby Stomp gem, you can get it with
|
170
|
+
`client.connection_frame().headers["session"]` where client is your
|
171
|
+
`Stomp::Client` instance; in my ABL jhstomp.p library call `get_session_id()`.
|
172
|
+
|
173
|
+
Now that you have a good reply ID you can tell which is yours by Rule 4; just
|
174
|
+
test the _neb-in-reply-to_ header of each message.
|
175
|
+
|
176
|
+
The second problem, of avoiding consuming messages that don't match your
|
177
|
+
reply-id, is handled by careful use of STOMP. If when subscribing you set the
|
178
|
+
header `ack:client-individual`, then you must manually acknowledge each message
|
179
|
+
you want to consume with an ACK frame.
|
180
|
+
|
181
|
+
Finally, you get to handle the response. Rule 1 says that it will either be an
|
182
|
+
error verb, a success verb ... or something else specific to the verb. The
|
183
|
+
nature of messages in responses is really outside of The Protocol; you are free
|
184
|
+
to use the verb / parameters / description format if you wish. Again, I'm
|
185
|
+
assuming that a given verb will always require the same parameters and return
|
186
|
+
the same message. I think that to do otherwise would be very confusing. But The
|
187
|
+
Protocol can't enforce that.
|
188
|
+
|
189
|
+
Note also that while The Protocol says that a request should always result in a
|
190
|
+
response, there is nothing to say that the sender of the request should care --
|
191
|
+
say, in the example of a request that results in a report being emailed, which
|
192
|
+
takes 20 minutes.
|
193
|
+
|
data/nebulous.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'nebulous/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "nebulous_stomp"
|
7
|
+
spec.version = Nebulous::VERSION
|
8
|
+
spec.authors = ["Andy Jones"]
|
9
|
+
spec.email = ["andy.jones@twosticksconsulting.co.uk"]
|
10
|
+
spec.summary = %q{Handles request-and-response messaging via STOMP}
|
11
|
+
spec.description = <<~DESC
|
12
|
+
A library and protocol to allow disperate systems to ask a question via
|
13
|
+
STOMP and receive an answer in return. Optionally, answers can be cached in
|
14
|
+
Redis.
|
15
|
+
DESC
|
16
|
+
|
17
|
+
spec.license = "MIT"
|
18
|
+
spec.homepage = "https://bitbucket.org/andy-twosticks/nebulous"
|
19
|
+
|
20
|
+
spec.files = `hg status -macn0`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.extra_rdoc_files = spec.files.grep(%r{^md/})
|
26
|
+
|
27
|
+
spec.requirements << 'STOMP Messaging server'
|
28
|
+
spec.requirements << 'Redis server (optional)'
|
29
|
+
|
30
|
+
spec.post_install_message = <<~MESSAGE
|
31
|
+
Nebulous has been installed ...sort of... ::waves arms noncomittedly::
|
32
|
+
MESSAGE
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
35
|
+
spec.add_development_dependency "rake", "~> 10.5"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
37
|
+
spec.add_development_dependency "rdoc"
|
38
|
+
spec.add_development_dependency "pry"
|
39
|
+
spec.add_development_dependency "pry-doc"
|
40
|
+
spec.add_development_dependency "ripper-tags"
|
41
|
+
|
42
|
+
spec.add_runtime_dependency 'stomp', '>=1.3'
|
43
|
+
spec.add_runtime_dependency 'redis', '>=3.1'
|
44
|
+
spec.add_runtime_dependency 'devnull', '~>0.1'
|
45
|
+
|
46
|
+
end
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Helpers
|
2
|
+
|
3
|
+
def stomp_message(contentType, body, inReplyTo=nil)
|
4
|
+
|
5
|
+
headers = { 'destination' => '/queue/foo',
|
6
|
+
'message-id' => '999',
|
7
|
+
'content-type' => contentType }
|
8
|
+
|
9
|
+
headers['neb-in-reply-to'] = inReplyTo if inReplyTo
|
10
|
+
|
11
|
+
mess = ['MESSAGE'] \
|
12
|
+
+ headers.map{|k,v| "#{k}:#{v}" } \
|
13
|
+
<< '' \
|
14
|
+
<< body
|
15
|
+
|
16
|
+
Stomp::Message.new( mess.join("\n") + "\0" )
|
17
|
+
end
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
|