ruby_nest_nats 0.1.3 → 0.2.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/README.md +108 -4
- data/lib/ruby_nest_nats.rb +5 -157
- data/lib/ruby_nest_nats/client.rb +170 -0
- data/lib/ruby_nest_nats/controller.rb +67 -0
- data/lib/ruby_nest_nats/utils.rb +17 -0
- data/lib/ruby_nest_nats/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c27d22ced425f310876883bb15afd2aaa52b24235bb095551b7981bedca4b210
|
4
|
+
data.tar.gz: 2537c259848226de1481ce0579b2036200b7d0e45a2a6b098fddd988bae16b4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8b7397a66a1f2984d54ae60bc3f299b384695f450a066cb00a64386fcfaf86be7dcad7e1c9e6f9fac4ede7994f41a2f1fa1cbd267563d5551dc2b53ac3512b0
|
7
|
+
data.tar.gz: a998c0f7015d4e6a1700ac23db6516cdd29572b92a1aaa18fe784b144862f5a229123820467a4d1e2e9baa5d5e30a618fbbe3bd526ac5e67b6640e08ca6b7e73
|
data/README.md
CHANGED
@@ -6,11 +6,13 @@ The `ruby_nest_nats` gem allows you to listen for (and reply to) NATS messages a
|
|
6
6
|
|
7
7
|
- [x] docs
|
8
8
|
- [ ] tests
|
9
|
-
- [
|
9
|
+
- [x] "controller"-style classes for reply organization
|
10
|
+
- [x] runtime subscription additions
|
10
11
|
- [x] multiple queues
|
11
12
|
- [ ] `on_error` handler so you can send a response (what's standard?)
|
12
13
|
- [ ] config options for URL/host/port/etc.
|
13
14
|
- [ ] config for restart behavior (default is to restart listening on any `StandardError`)
|
15
|
+
- [ ] consider using child processes instead of threads
|
14
16
|
|
15
17
|
## Installation
|
16
18
|
|
@@ -36,6 +38,10 @@ Alternatively, install it globally:
|
|
36
38
|
gem install ruby_nest_nats
|
37
39
|
```
|
38
40
|
|
41
|
+
### NATS server
|
42
|
+
|
43
|
+
**IMPORTANT:** This gem also requires a NATS server to be installed and running before use. See [the NATS documentation](https://docs.nats.io/nats-server/installation) for more details.
|
44
|
+
|
39
45
|
## Usage
|
40
46
|
|
41
47
|
### Logging
|
@@ -45,6 +51,7 @@ gem install ruby_nest_nats
|
|
45
51
|
Attach a logger to have `ruby_nest_nats` write out logs for messages received, responses sent, errors raised, lifecycle events, etc.
|
46
52
|
|
47
53
|
```rb
|
54
|
+
require 'ruby_nest_nats'
|
48
55
|
require 'logger'
|
49
56
|
|
50
57
|
nats_logger = Logger.new(STDOUT)
|
@@ -68,6 +75,8 @@ The following will be logged at the specified log levels
|
|
68
75
|
- `WARN`: Error handled gracefully (listening restarted due to some exception, etc.), as well as everything under `ERROR`
|
69
76
|
- `ERROR`: Some exception was raised in-thread (error in handler, error in subscription, etc.)
|
70
77
|
|
78
|
+
<a id="default-queue-section"></a>
|
79
|
+
|
71
80
|
### Setting a default queue
|
72
81
|
|
73
82
|
Set a default queue for subscriptions.
|
@@ -82,6 +91,8 @@ Leave the `::default_queue` blank (or assign `nil`) to use no default queue.
|
|
82
91
|
RubyNestNats::Client.default_queue = nil
|
83
92
|
```
|
84
93
|
|
94
|
+
<a id="reply-to-section"></a>
|
95
|
+
|
85
96
|
### Registering message handlers
|
86
97
|
|
87
98
|
Register a message handler with the `RubyNestNats::Client::reply_to` method. Pass a subject string as the first argument (either a static subject string or a pattern to match more than one subject). Specify a queue (or don't) with the `queue:` option. If you don't provide the `queue:` option, it will be set to the value of `default_queue`, or to `nil` (no queue) if a default queue hasn't been set.
|
@@ -116,10 +127,20 @@ Start listening for messages with the `RubyNestNats::Client::start!` method. Thi
|
|
116
127
|
RubyNestNats::Client.start!
|
117
128
|
```
|
118
129
|
|
119
|
-
###
|
130
|
+
### Basic full working example (in vanilla Ruby)
|
131
|
+
|
132
|
+
The following should be enough to start a `ruby_nest_nats` setup in your Ruby application, using what we've learned so far.
|
133
|
+
|
134
|
+
> **NOTE:** For a more organized structure and implementation in a larger app (like a Rails project), see the ["controller" section below](#controller-section).
|
120
135
|
|
121
136
|
```rb
|
122
|
-
|
137
|
+
require 'ruby_nest_nats'
|
138
|
+
require 'logger'
|
139
|
+
|
140
|
+
nats_logger = Logger.new(STDOUT)
|
141
|
+
nats_logger.level = Logger::DEBUG
|
142
|
+
|
143
|
+
RubyNestNats::Client.logger = nats_logger
|
123
144
|
RubyNestNats::Client.default_queue = "foobar"
|
124
145
|
|
125
146
|
RubyNestNats::Client.reply_to("some.subject") { |data| "Got it! #{data.inspect}" }
|
@@ -129,6 +150,86 @@ RubyNestNats::Client.reply_to("subject.in.queue", queue: "barbaz") { { msg: "My
|
|
129
150
|
RubyNestNats::Client.start!
|
130
151
|
```
|
131
152
|
|
153
|
+
> **NOTE:** You _can_ invoke `::reply_to` to create additional message subscriptions after `RubyNestNats::Client.start!`, but be aware that this forces the client to restart. You may see (benign, already-handled) errors in the logs generated when this restart happens. It will force the client to restart and re-subscribe after _each additional `::reply_to` invoked after `::start!`._ So, if you have a lot of additional `::reply_to` invocations, you may want to consider refactoring so that your call to `RubyNestNats::Client.start!` occurs _after_ those additions.
|
154
|
+
|
155
|
+
> **NOTE:** The `::start!` method can be safely called multiple times; only the first will be honored, and any subsequent calls to `::start!` after the client is already started will do nothing (except write a _"NATS is already running"_ log to the logger at the `DEBUG` level).
|
156
|
+
|
157
|
+
<a id="controller-section"></a>
|
158
|
+
|
159
|
+
### Creating "controller"-style classes for listener organization
|
160
|
+
|
161
|
+
Create controller classes which inherit from `RubyNestNats::Controller` in order to give your message listeners some structure.
|
162
|
+
|
163
|
+
Use the `::default_queue` macro to set a default queue string. If omitted, the controller will fall back on the default queue assigned with `RubyNestNats::Client::default_queue=` (as described [here](#default-queue-section)). If no default queue is set in either the controller or globally, then the default queue will be blank.
|
164
|
+
|
165
|
+
Use the `::subject` macro to create a block for listening to that subject segment. Nested calls to `::subject` will append each subsequent subject/pattern string to the last (joined by a periods). There is no limit to the level of nesting.
|
166
|
+
|
167
|
+
You can register a response for the built-up subject/pattern string using the `::response` macro. Pass a block to `::response` which optionally takes two arguments ([the same arguments supplied to `RubyNestNats::Client::reply_to`](#reply-to-section)). The result of that block will be sent as a response to the message received.
|
168
|
+
|
169
|
+
```rb
|
170
|
+
class HelloController < RubyNestNats::Controller
|
171
|
+
default_queue "foobar"
|
172
|
+
|
173
|
+
subject "hello" do
|
174
|
+
subject "jerk" do
|
175
|
+
response do |data|
|
176
|
+
# The subject at this point is "hello.jerk"
|
177
|
+
"Hey #{data['name']}... that's not cool, man."
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
subject "and" do
|
182
|
+
subject "wassup" do
|
183
|
+
response do |data|
|
184
|
+
# The subject at this point is "hello.and.wassup"
|
185
|
+
"Hey, how ya doin', #{data['name']}?"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
subject "goodbye" do
|
190
|
+
response do |data|
|
191
|
+
# The subject at this point is "hello.and.goodbye"
|
192
|
+
"Hi #{data['name']}! But also GOODBYE."
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
subject "hows" do
|
199
|
+
subject "*" do
|
200
|
+
subject "doing" do
|
201
|
+
response do |data, subject|
|
202
|
+
# The subject at this point is "hows.<wildcard>.doing" (i.e., the
|
203
|
+
# subjects "hows.jack.doing" and "hows.jill.doing" will both match)
|
204
|
+
sender_name = data["name"]
|
205
|
+
other_person_name = subject.split(".")[1]
|
206
|
+
desc = rand < 0.5 ? "terribly" : "great"
|
207
|
+
"Well, #{sender_name}, #{other_person_name} is actually doing #{desc}."
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
> **NOTE:** If you implement controllers like this and you are using code-autoloading machinery (like Zeitwerk in Rails), you will need to make sure these paths are eager-loaded when your app starts. **If you don't, `ruby_nest_nats` will not register the listeners,** and will not respond to messages for the specified subjects.
|
216
|
+
>
|
217
|
+
> For example: in a Rails project (assuming you have your NATS controllers in a directory called `app/nats/`), you may want to put something like the following in an initializer (such as `config/initializers/nats.rb`):
|
218
|
+
>
|
219
|
+
> ```rb
|
220
|
+
> RubyNestNats::Client.logger = Rails.logger
|
221
|
+
> RubyNestNats::Client.default_queue = "foobar"
|
222
|
+
>
|
223
|
+
> # ...
|
224
|
+
>
|
225
|
+
> Rails.application.config.after_initialize do
|
226
|
+
> nats_controller_paths = Dir[Rails.root.join("app", "nats", "**", "*_controller.rb")]
|
227
|
+
> nats_controller_paths.each { |file_path| require_dependency(file_path) }
|
228
|
+
>
|
229
|
+
> RubyNestNats::Client.start!
|
230
|
+
> end
|
231
|
+
> ```
|
232
|
+
|
132
233
|
## Development
|
133
234
|
|
134
235
|
### Install dependencies
|
@@ -139,7 +240,10 @@ To install the Ruby dependencies, run:
|
|
139
240
|
bin/setup
|
140
241
|
```
|
141
242
|
|
142
|
-
This gem also requires a NATS server to be running. See [the NATS documentation](https://docs.nats.io/nats-server/installation) for more details.
|
243
|
+
This gem also requires a NATS server to be installed and running. See [the NATS documentation](https://docs.nats.io/nats-server/installation) for more details.
|
244
|
+
<!-- sudo docker run -p 4222:4222 -p 8222:8222 -p 6222:6222 -ti nats:latest -->
|
245
|
+
<!-- nats-tail -s nats://localhost:4222 ">" -->
|
246
|
+
<!-- curl --data '{"name":"Keegan"}' --header 'Content-Type: application/json' http://localhost:3000/hello -->
|
143
247
|
|
144
248
|
### Open a console
|
145
249
|
|
data/lib/ruby_nest_nats.rb
CHANGED
@@ -1,164 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "ruby_nest_nats/version"
|
4
3
|
require "nats/client"
|
4
|
+
require_relative "ruby_nest_nats/version"
|
5
|
+
require_relative "ruby_nest_nats/utils"
|
6
|
+
require_relative "ruby_nest_nats/client"
|
7
|
+
require_relative "ruby_nest_nats/controller"
|
5
8
|
|
6
9
|
module RubyNestNats
|
7
10
|
class Error < StandardError; end
|
8
|
-
|
9
|
-
class Client
|
10
|
-
class << self
|
11
|
-
def logger=(some_logger)
|
12
|
-
log("Setting the logger to #{some_logger.inspect}")
|
13
|
-
@logger = some_logger
|
14
|
-
end
|
15
|
-
|
16
|
-
def logger
|
17
|
-
@logger
|
18
|
-
end
|
19
|
-
|
20
|
-
def default_queue=(some_queue)
|
21
|
-
queue = presence(some_queue.to_s)
|
22
|
-
log("Setting the default queue to #{queue}", level: :debug)
|
23
|
-
@default_queue = queue
|
24
|
-
end
|
25
|
-
|
26
|
-
def default_queue
|
27
|
-
@default_queue
|
28
|
-
end
|
29
|
-
|
30
|
-
def started?
|
31
|
-
@started ||= false
|
32
|
-
end
|
33
|
-
|
34
|
-
def stopped?
|
35
|
-
!started?
|
36
|
-
end
|
37
|
-
|
38
|
-
def reply_to(subject, queue: nil, &block)
|
39
|
-
subject = subject.to_s
|
40
|
-
queue = (presence(queue) || default_queue).to_s
|
41
|
-
log("Registering a reply handler for subject '#{subject}'#{" in queue '#{queue}'" if queue}", level: :debug)
|
42
|
-
register_reply!(subject: subject, handler: block, queue: queue)
|
43
|
-
end
|
44
|
-
|
45
|
-
def listen
|
46
|
-
NATS.start do
|
47
|
-
replies.each do |replier|
|
48
|
-
log("Subscribing to subject '#{replier[:subject]}'#{" in queue '#{replier[:queue]}'" if replier[:queue]}", level: :debug)
|
49
|
-
|
50
|
-
NATS.subscribe(replier[:subject], queue: replier[:queue]) do |message, inbox, subject|
|
51
|
-
parsed_message = JSON.parse(message)
|
52
|
-
id, data, pattern = parsed_message.values_at("id", "data", "pattern")
|
53
|
-
|
54
|
-
log("Received a message!")
|
55
|
-
message_desc = <<~LOG_MESSAGE
|
56
|
-
id: #{id || '(none)'}
|
57
|
-
pattern: #{pattern || '(none)'}
|
58
|
-
subject: #{subject || '(none)'}
|
59
|
-
data: #{data.to_json}
|
60
|
-
inbox: #{inbox || '(none)'}
|
61
|
-
LOG_MESSAGE
|
62
|
-
log(message_desc, indent: 2)
|
63
|
-
|
64
|
-
response_data = replier[:handler].call(data)
|
65
|
-
|
66
|
-
log("Responding with '#{response_data}'")
|
67
|
-
|
68
|
-
NATS.publish(inbox, response_data.to_json, queue: replier[:queue])
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def stop!
|
75
|
-
log("Stopping NATS", level: :debug)
|
76
|
-
NATS.stop rescue nil
|
77
|
-
stopped!
|
78
|
-
end
|
79
|
-
|
80
|
-
def restart!
|
81
|
-
log("Restarting NATS", level: :warn)
|
82
|
-
stop!
|
83
|
-
start!
|
84
|
-
end
|
85
|
-
|
86
|
-
def start!
|
87
|
-
log("Starting NATS", level: :debug)
|
88
|
-
|
89
|
-
if started?
|
90
|
-
log("NATS is already running", level: :debug)
|
91
|
-
return
|
92
|
-
end
|
93
|
-
|
94
|
-
started!
|
95
|
-
|
96
|
-
Thread.new do
|
97
|
-
Thread.handle_interrupt(StandardError => :never) do
|
98
|
-
begin
|
99
|
-
Thread.handle_interrupt(StandardError => :immediate) { listen }
|
100
|
-
rescue => e
|
101
|
-
log("Encountered an error:", level: :error)
|
102
|
-
log(e.full_message, level: :error, indent: 2)
|
103
|
-
|
104
|
-
restart!
|
105
|
-
raise e
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
def log(text, level: :info, indent: 0)
|
114
|
-
return unless logger
|
115
|
-
|
116
|
-
timestamp = Time.now.to_s
|
117
|
-
text_lines = text.split("\n")
|
118
|
-
indentation = indent.is_a?(String) ? indent : (" " * indent)
|
119
|
-
|
120
|
-
text_lines.each do |line|
|
121
|
-
logger.send(level, "[#{timestamp}] RubyNestNats | #{indentation}#{line}")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def started!
|
126
|
-
@started = true
|
127
|
-
end
|
128
|
-
|
129
|
-
def stopped!
|
130
|
-
@started = false
|
131
|
-
end
|
132
|
-
|
133
|
-
def replies
|
134
|
-
@replies ||= []
|
135
|
-
end
|
136
|
-
|
137
|
-
def reply_registered?(raw_subject)
|
138
|
-
subject = raw_subject.to_s
|
139
|
-
replies.any? { |reply| reply[:subject] == subject }
|
140
|
-
end
|
141
|
-
|
142
|
-
def register_reply!(subject:, handler:, queue: nil)
|
143
|
-
raise StandardError, "NATS already started" if started? # TODO: remove when runtime additions are implemented
|
144
|
-
raise ArgumentError, "Subject must be a string" unless subject.is_a?(String)
|
145
|
-
raise ArgumentError, "Must provide a message handler for #{subject}" unless handler.respond_to?(:call)
|
146
|
-
raise ArgumentError, "Already registered a reply to #{subject}" if reply_registered?(subject)
|
147
|
-
|
148
|
-
replies << { subject: subject, handler: handler, queue: presence(queue) || default_queue }
|
149
|
-
end
|
150
|
-
|
151
|
-
def blank?(value)
|
152
|
-
value.respond_to?(:empty?) ? value.empty? : !value
|
153
|
-
end
|
154
|
-
|
155
|
-
def present?(value)
|
156
|
-
!blank?(value)
|
157
|
-
end
|
158
|
-
|
159
|
-
def presence(value)
|
160
|
-
present?(value) ? value : nil
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
11
|
+
class NewSubscriptionsError < StandardError; end
|
164
12
|
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require "json"
|
2
|
+
require "nats/client"
|
3
|
+
require_relative "./utils"
|
4
|
+
|
5
|
+
module RubyNestNats
|
6
|
+
class Client
|
7
|
+
class << self
|
8
|
+
def logger=(some_logger)
|
9
|
+
log("Setting the logger to #{some_logger.inspect}")
|
10
|
+
@logger = some_logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def logger
|
14
|
+
@logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_queue=(some_queue)
|
18
|
+
queue = Utils.presence(some_queue.to_s)
|
19
|
+
log("Setting the default queue to #{queue || '(none)'}", level: :debug)
|
20
|
+
@default_queue = queue
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_queue
|
24
|
+
@default_queue
|
25
|
+
end
|
26
|
+
|
27
|
+
def started?
|
28
|
+
@started ||= false
|
29
|
+
end
|
30
|
+
|
31
|
+
def stopped?
|
32
|
+
!started?
|
33
|
+
end
|
34
|
+
|
35
|
+
def reply_to(subject, queue: nil, &block)
|
36
|
+
queue = Utils.presence(queue) || default_queue
|
37
|
+
log("Registering a reply handler for subject '#{subject}'#{" in queue '#{queue}'" if queue}", level: :debug)
|
38
|
+
register_reply!(subject: subject.to_s, handler: block, queue: queue.to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
def start!
|
42
|
+
log("Starting NATS", level: :debug)
|
43
|
+
|
44
|
+
if started?
|
45
|
+
log("NATS is already running", level: :debug)
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
started!
|
50
|
+
|
51
|
+
self.current_thread = Thread.new do
|
52
|
+
Thread.handle_interrupt(StandardError => :never) do
|
53
|
+
begin
|
54
|
+
Thread.handle_interrupt(StandardError => :immediate) { listen }
|
55
|
+
rescue NATS::ConnectError => e
|
56
|
+
log("Could not connect to NATS server:", level: :error)
|
57
|
+
log(e.full_message, level: :error, indent: 2)
|
58
|
+
Thread.current.exit
|
59
|
+
rescue NewSubscriptionsError => e
|
60
|
+
log("New subscriptions! Restarting...", level: :info)
|
61
|
+
restart!
|
62
|
+
raise e # TODO: there has to be a better way
|
63
|
+
rescue StandardError => e
|
64
|
+
log("Encountered an error:", level: :error)
|
65
|
+
log(e.full_message, level: :error, indent: 2)
|
66
|
+
restart!
|
67
|
+
raise e
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def log(text, level: :info, indent: 0)
|
76
|
+
return unless logger
|
77
|
+
|
78
|
+
timestamp = Time.now.to_s
|
79
|
+
text_lines = text.split("\n")
|
80
|
+
indentation = indent.is_a?(String) ? indent : (" " * indent)
|
81
|
+
|
82
|
+
text_lines.each do |line|
|
83
|
+
logger.send(level, "[#{timestamp}] RubyNestNats | #{indentation}#{line}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def stop!
|
88
|
+
log("Stopping NATS", level: :debug)
|
89
|
+
NATS.stop rescue nil
|
90
|
+
stopped!
|
91
|
+
end
|
92
|
+
|
93
|
+
def restart!
|
94
|
+
log("Restarting NATS", level: :warn)
|
95
|
+
stop!
|
96
|
+
start!
|
97
|
+
end
|
98
|
+
|
99
|
+
def started!
|
100
|
+
@started = true
|
101
|
+
end
|
102
|
+
|
103
|
+
def stopped!
|
104
|
+
@started = false
|
105
|
+
end
|
106
|
+
|
107
|
+
def replies
|
108
|
+
@replies ||= []
|
109
|
+
end
|
110
|
+
|
111
|
+
def current_thread
|
112
|
+
@current_thread
|
113
|
+
end
|
114
|
+
|
115
|
+
def current_thread=(some_thread)
|
116
|
+
@current_thread = some_thread
|
117
|
+
end
|
118
|
+
|
119
|
+
def reply_registered?(raw_subject)
|
120
|
+
subject = raw_subject.to_s
|
121
|
+
replies.any? { |reply| reply[:subject] == subject }
|
122
|
+
end
|
123
|
+
|
124
|
+
def register_reply!(subject:, handler:, queue: nil)
|
125
|
+
raise ArgumentError, "Subject must be a string" unless subject.is_a?(String)
|
126
|
+
raise ArgumentError, "Must provide a message handler for #{subject}" unless handler.respond_to?(:call)
|
127
|
+
raise ArgumentError, "Already registered a reply to #{subject}" if reply_registered?(subject)
|
128
|
+
|
129
|
+
reply = {
|
130
|
+
subject: subject,
|
131
|
+
handler: handler,
|
132
|
+
queue: Utils.presence(queue) || default_queue
|
133
|
+
}
|
134
|
+
|
135
|
+
replies << reply
|
136
|
+
|
137
|
+
self.current_thread.raise(NewSubscriptionsError, "New reply registered") if started?
|
138
|
+
end
|
139
|
+
|
140
|
+
def listen
|
141
|
+
NATS.start do
|
142
|
+
replies.each do |replier|
|
143
|
+
log("Subscribing to subject '#{replier[:subject]}'#{" in queue '#{replier[:queue]}'" if replier[:queue]}", level: :debug)
|
144
|
+
|
145
|
+
NATS.subscribe(replier[:subject], queue: replier[:queue]) do |message, inbox, subject|
|
146
|
+
parsed_message = JSON.parse(message)
|
147
|
+
id, data, pattern = parsed_message.values_at("id", "data", "pattern")
|
148
|
+
|
149
|
+
log("Received a message!")
|
150
|
+
message_desc = <<~LOG_MESSAGE
|
151
|
+
id: #{id || '(none)'}
|
152
|
+
pattern: #{pattern || '(none)'}
|
153
|
+
subject: #{subject || '(none)'}
|
154
|
+
data: #{data.to_json}
|
155
|
+
inbox: #{inbox || '(none)'}
|
156
|
+
LOG_MESSAGE
|
157
|
+
log(message_desc, indent: 2)
|
158
|
+
|
159
|
+
response_data = replier[:handler].call(data)
|
160
|
+
|
161
|
+
log("Responding with '#{response_data}'")
|
162
|
+
|
163
|
+
NATS.publish(inbox, response_data.to_json, queue: replier[:queue])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative "./utils"
|
2
|
+
|
3
|
+
module RubyNestNats
|
4
|
+
class Controller
|
5
|
+
NO_QUEUE_GIVEN = :ruby_nest_nats_super_special_no_op_queue_symbol_qwertyuiop_1234567890
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Default queue for the controller. Falls back to the client's default
|
9
|
+
# queue if the controller's default queue is `nil`.
|
10
|
+
#
|
11
|
+
# - Call with no argument (`::default_queue`) to get the default queue.
|
12
|
+
# - Call as a macro with an argument (`default_queue "something"`) to set
|
13
|
+
# the default queue.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# class FoobarNatsController < RubyNatsController
|
18
|
+
# default_queue "foobar"
|
19
|
+
#
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
def default_queue(some_queue = NO_QUEUE_GIVEN)
|
24
|
+
# `NO_QUEUE_GIVEN` is a special symbol (rather than `nil`) so that the
|
25
|
+
# default queue can be "unset" to `nil` (given a non-`nil` global
|
26
|
+
# default set with `RubyNestNats::Client::default_queue=`).
|
27
|
+
if some_queue == NO_QUEUE_GIVEN
|
28
|
+
@default_queue || Client.default_queue
|
29
|
+
else
|
30
|
+
@default_queue = Utils.presence(some_queue.to_s)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def subject(subject_segment, queue: nil)
|
35
|
+
subject_chain.push(subject_segment)
|
36
|
+
old_queue = current_queue
|
37
|
+
self.current_queue = queue if Utils.present?(queue)
|
38
|
+
yield
|
39
|
+
self.current_queue = old_queue
|
40
|
+
subject_chain.pop
|
41
|
+
end
|
42
|
+
|
43
|
+
def response(queue: nil, &block)
|
44
|
+
response_queue = Utils.presence(queue.to_s) || current_queue || default_queue
|
45
|
+
Client.reply_to(current_subject, queue: response_queue, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def subject_chain
|
51
|
+
@subject_chain ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_subject
|
55
|
+
subject_chain.join(".")
|
56
|
+
end
|
57
|
+
|
58
|
+
def current_queue
|
59
|
+
@current_queue ||= nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def current_queue=(some_queue)
|
63
|
+
@current_queue = Utils.presence(some_queue)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RubyNestNats
|
2
|
+
class Utils
|
3
|
+
class << self
|
4
|
+
def blank?(value)
|
5
|
+
value.respond_to?(:empty?) ? value.empty? : !value
|
6
|
+
end
|
7
|
+
|
8
|
+
def present?(value)
|
9
|
+
!blank?(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def presence(value)
|
13
|
+
present?(value) ? value : nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_nest_nats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keegan Leitz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -155,6 +155,9 @@ files:
|
|
155
155
|
- bin/console
|
156
156
|
- bin/setup
|
157
157
|
- lib/ruby_nest_nats.rb
|
158
|
+
- lib/ruby_nest_nats/client.rb
|
159
|
+
- lib/ruby_nest_nats/controller.rb
|
160
|
+
- lib/ruby_nest_nats/utils.rb
|
158
161
|
- lib/ruby_nest_nats/version.rb
|
159
162
|
- ruby_nest_nats.gemspec
|
160
163
|
homepage: https://github.com/openbay/ruby_nest_nats
|