nats-streaming 0.1.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 +7 -0
- data/lib/stan/client.rb +531 -0
- data/lib/stan/pb/protocol.pb.rb +97 -0
- data/lib/stan/version.rb +3 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2b4619f24307110860a56b03d04b15be8c8f35b3
|
4
|
+
data.tar.gz: 79e03889f056cfbd5fb64491c236ffe66b99f4e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64d4045d951790d2207aa80cc5732b0f08078f7fe244f5ea033663c74a70f901d78e5015758300fa198a7fb35457fb70ad0f5a311ca0be83a6ccaae37ef745f4
|
7
|
+
data.tar.gz: 7cbbcef0b8ab55e53b8ca2124427ff41f77356eb326d775106249ea58c880cea88dd065657df323289abea2e7dbae517cb6383be4a26133a35857355b6cfbafc
|
data/lib/stan/client.rb
ADDED
@@ -0,0 +1,531 @@
|
|
1
|
+
require 'stan/pb/protocol.pb'
|
2
|
+
require 'nats/io/client'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'monitor'
|
5
|
+
|
6
|
+
module STAN
|
7
|
+
|
8
|
+
# Subject namespaces for clients to ack and connect
|
9
|
+
DEFAULT_ACKS_SUBJECT = "_STAN.acks".freeze
|
10
|
+
DEFAULT_DISCOVER_SUBJECT = "_STAN.discover".freeze
|
11
|
+
|
12
|
+
# Ack timeout in seconds
|
13
|
+
DEFAULT_ACK_WAIT = 30
|
14
|
+
|
15
|
+
# Max number of inflight acks from received messages
|
16
|
+
DEFAULT_MAX_INFLIGHT = 1024
|
17
|
+
|
18
|
+
# Connect timeout in seconds
|
19
|
+
DEFAULT_CONNECT_TIMEOUT = 2
|
20
|
+
|
21
|
+
# Max number of inflight pub acks
|
22
|
+
DEFAULT_MAX_PUB_ACKS_INFLIGHT = 16384
|
23
|
+
|
24
|
+
# Errors
|
25
|
+
class Error < StandardError; end
|
26
|
+
|
27
|
+
# When we detect we cannot connect to the server
|
28
|
+
class ConnectError < Error; end
|
29
|
+
|
30
|
+
# When we detect we have a request timeout
|
31
|
+
class TimeoutError < Error; end
|
32
|
+
|
33
|
+
class Client
|
34
|
+
include MonitorMixin
|
35
|
+
|
36
|
+
attr_reader :nats, :options, :client_id, :sub_map, :unsub_req_subject, :sub_close_req_subject, :pending_pub_acks
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
super
|
40
|
+
|
41
|
+
# Connection to NATS, either owned or borrowed
|
42
|
+
@nats = nil
|
43
|
+
@borrowed_nats_connection = false
|
44
|
+
|
45
|
+
# STAN subscriptions map
|
46
|
+
@sub_map = {}
|
47
|
+
|
48
|
+
# Publish Ack map (guid => ack)
|
49
|
+
@pub_ack_map = {}
|
50
|
+
@pending_pub_acks = nil
|
51
|
+
|
52
|
+
# Cluster to which we are connecting
|
53
|
+
@cluster_id = nil
|
54
|
+
@client_id = nil
|
55
|
+
|
56
|
+
# Connect options
|
57
|
+
@options = {}
|
58
|
+
|
59
|
+
# NATS Streaming subjects
|
60
|
+
|
61
|
+
# Inbox subscription for periodical heartbeat messages
|
62
|
+
@hb_inbox = nil
|
63
|
+
@hb_inbox_sid = nil
|
64
|
+
|
65
|
+
# Subscription for processing received acks from the server
|
66
|
+
@ack_subject = nil
|
67
|
+
@ack_subject_sid = nil
|
68
|
+
|
69
|
+
# Publish prefix set by stan to which we append our subject on publish.
|
70
|
+
@pub_prefix = nil
|
71
|
+
@sub_req_subject = nil
|
72
|
+
@unsub_req_subject = nil
|
73
|
+
@close_req_subject = nil
|
74
|
+
@sub_close_req_subject = nil
|
75
|
+
|
76
|
+
# For initial connect request to discover subjects used by
|
77
|
+
# the streaming server.
|
78
|
+
@discover_subject = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# Plugs into a NATS Streaming cluster, establishing a connection
|
82
|
+
# to NATS in case there is not one available to be borrowed.
|
83
|
+
def connect(cluster_id, client_id, opts={}, &blk)
|
84
|
+
@cluster_id = cluster_id
|
85
|
+
@client_id = client_id
|
86
|
+
@options = opts
|
87
|
+
|
88
|
+
# Defaults
|
89
|
+
@options[:connect_timeout] ||= DEFAULT_CONNECT_TIMEOUT
|
90
|
+
@options[:max_pub_acks_inflight] ||= DEFAULT_MAX_PUB_ACKS_INFLIGHT
|
91
|
+
|
92
|
+
# Buffered queue for controlling inflight published acks
|
93
|
+
@pending_pub_acks = SizedQueue.new(options[:max_pub_acks_inflight])
|
94
|
+
|
95
|
+
# Prepare connect discovery request
|
96
|
+
@discover_subject = "#{DEFAULT_DISCOVER_SUBJECT}.#{@cluster_id}".freeze
|
97
|
+
|
98
|
+
# Prepare delivered msgs acks processing subscription
|
99
|
+
@ack_subject = "#{DEFAULT_ACKS_SUBJECT}.#{STAN.create_guid}".freeze
|
100
|
+
|
101
|
+
if @nats.nil?
|
102
|
+
case options[:nats]
|
103
|
+
when Hash
|
104
|
+
# Custom NATS options in case borrowed connection not present
|
105
|
+
# can be passed to establish a connection and have stan client
|
106
|
+
# owning it.
|
107
|
+
@nats = NATS::IO::Client.new
|
108
|
+
nats.connect(options[:nats])
|
109
|
+
when NATS::IO::Client
|
110
|
+
@nats = options[:nats]
|
111
|
+
@borrowed_nats_connection = true
|
112
|
+
else
|
113
|
+
# Try to connect with NATS defaults
|
114
|
+
@nats = NATS::IO::Client.new
|
115
|
+
nats.connect(servers: ["nats://127.0.0.1:4222"])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# If no connection to NATS present at this point then bail already
|
120
|
+
raise ConnectError.new("stan: invalid connection to nats") unless @nats
|
121
|
+
|
122
|
+
# Heartbeat subscription
|
123
|
+
@hb_inbox = (STAN.create_inbox).freeze
|
124
|
+
|
125
|
+
# Setup acks and heartbeats processing callbacks
|
126
|
+
@hb_inbox_sid = nats.subscribe(@hb_inbox) { |raw| process_heartbeats(raw) }
|
127
|
+
@ack_subject_sid = nats.subscribe(@ack_subject) { |raw| process_ack(raw) }
|
128
|
+
nats.flush
|
129
|
+
|
130
|
+
# Initial connect request to discover subjects to be used
|
131
|
+
# for communicating with STAN.
|
132
|
+
req = STAN::Protocol::ConnectRequest.new({
|
133
|
+
clientID: @client_id,
|
134
|
+
heartbeatInbox: @hb_inbox
|
135
|
+
})
|
136
|
+
|
137
|
+
# TODO: Check for error and bail if required
|
138
|
+
raw = nats.request(@discover_subject, req.to_proto, timeout: options[:connect_timeout])
|
139
|
+
resp = STAN::Protocol::ConnectResponse.decode(raw.data)
|
140
|
+
@pub_prefix = resp.pubPrefix.freeze
|
141
|
+
@sub_req_subject = resp.subRequests.freeze
|
142
|
+
@unsub_req_subject = resp.unsubRequests.freeze
|
143
|
+
@close_req_subject = resp.closeRequests.freeze
|
144
|
+
@sub_close_req_subject = resp.subCloseRequests.freeze
|
145
|
+
|
146
|
+
# If callback given then we send a close request on exit
|
147
|
+
# and wrap up session to STAN.
|
148
|
+
if blk
|
149
|
+
blk.call(self)
|
150
|
+
|
151
|
+
# Close session to the STAN cluster
|
152
|
+
close
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Publish will publish to the cluster and wait for an ack
|
157
|
+
def publish(subject, payload, opts={}, &blk)
|
158
|
+
stan_subject = "#{@pub_prefix}.#{subject}"
|
159
|
+
future = nil
|
160
|
+
guid = STAN.create_guid
|
161
|
+
|
162
|
+
pe = STAN::Protocol::PubMsg.new({
|
163
|
+
clientID: @client_id,
|
164
|
+
guid: guid,
|
165
|
+
subject: subject,
|
166
|
+
data: payload
|
167
|
+
})
|
168
|
+
|
169
|
+
# Use buffered queue to control number of outstanding acks
|
170
|
+
@pending_pub_acks << :ack
|
171
|
+
|
172
|
+
if blk
|
173
|
+
# Asynchronously handled if block given
|
174
|
+
synchronize do
|
175
|
+
# Map ack to guid
|
176
|
+
@pub_ack_map[guid] = proc do |ack|
|
177
|
+
# If block is given, handle the result asynchronously
|
178
|
+
error = ack.error.empty? ? nil : Error.new(ack.error)
|
179
|
+
case blk.arity
|
180
|
+
when 0 then blk.call
|
181
|
+
when 1 then blk.call(ack.guid)
|
182
|
+
when 2 then blk.call(ack.guid, error)
|
183
|
+
end
|
184
|
+
|
185
|
+
@pub_ack_map.delete(ack.guid)
|
186
|
+
end
|
187
|
+
|
188
|
+
nats.publish(stan_subject, pe.to_proto, @ack_subject)
|
189
|
+
end
|
190
|
+
else
|
191
|
+
# No block means waiting for response before giving back control
|
192
|
+
future = new_cond
|
193
|
+
opts[:timeout] ||= DEFAULT_ACK_WAIT
|
194
|
+
|
195
|
+
synchronize do
|
196
|
+
# Map ack to guid
|
197
|
+
ack_response = nil
|
198
|
+
|
199
|
+
# FIXME: Maybe use fiber instead?
|
200
|
+
@pub_ack_map[guid] = proc do |ack|
|
201
|
+
# Capture the ack response
|
202
|
+
ack_response = ack
|
203
|
+
future.signal
|
204
|
+
end
|
205
|
+
|
206
|
+
# Send publish request and wait for the ack response
|
207
|
+
nats.publish(stan_subject, pe.to_proto, @ack_subject)
|
208
|
+
start_time = NATS::MonotonicTime.now
|
209
|
+
future.wait(opts[:timeout])
|
210
|
+
end_time = NATS::MonotonicTime.now
|
211
|
+
if (end_time - start_time) > opts[:timeout]
|
212
|
+
# Remove ack
|
213
|
+
@pub_ack_map.delete(guid)
|
214
|
+
raise TimeoutError.new("stan: timeout")
|
215
|
+
end
|
216
|
+
|
217
|
+
# Remove ack
|
218
|
+
@pub_ack_map.delete(guid)
|
219
|
+
return guid
|
220
|
+
end
|
221
|
+
end
|
222
|
+
# TODO: Loop for processing of expired acks
|
223
|
+
end
|
224
|
+
|
225
|
+
# Create subscription which dispatches messages to callback asynchronously
|
226
|
+
def subscribe(subject, opts={}, &cb)
|
227
|
+
sub_options = {}
|
228
|
+
sub_options.merge!(opts)
|
229
|
+
sub_options[:ack_wait] ||= DEFAULT_ACK_WAIT
|
230
|
+
sub_options[:max_inflight] ||= DEFAULT_MAX_INFLIGHT
|
231
|
+
sub_options[:stan] = self
|
232
|
+
|
233
|
+
sub = Subscription.new(subject, sub_options, cb)
|
234
|
+
sub.extend(MonitorMixin)
|
235
|
+
synchronize { @sub_map[sub.inbox] = sub }
|
236
|
+
|
237
|
+
# Hold lock throughout
|
238
|
+
sub.synchronize do
|
239
|
+
# Listen for actual messages
|
240
|
+
sid = nats.subscribe(sub.inbox) { |raw, reply, subject| process_msg(raw, reply, subject) }
|
241
|
+
sub.sid = sid
|
242
|
+
nats.flush
|
243
|
+
|
244
|
+
# Create the subscription request announcing the inbox on which
|
245
|
+
# we have made the NATS subscription for processing messages.
|
246
|
+
# First, we normalize customized subscription options before
|
247
|
+
# encoding to protobuf.
|
248
|
+
sub_opts = normalize_sub_req_params(sub_options)
|
249
|
+
|
250
|
+
# Set STAN subject and NATS inbox where we will be awaiting
|
251
|
+
# for the messages to be delivered.
|
252
|
+
sub_opts[:subject] = subject
|
253
|
+
sub_opts[:inbox] = sub.inbox
|
254
|
+
|
255
|
+
sr = STAN::Protocol::SubscriptionRequest.new(sub_opts)
|
256
|
+
reply = nil
|
257
|
+
response = nil
|
258
|
+
begin
|
259
|
+
reply = nats.request(@sub_req_subject, sr.to_proto, timeout: options[:connect_timeout])
|
260
|
+
response = STAN::Protocol::SubscriptionResponse.decode(reply.data)
|
261
|
+
rescue NATS::IO::Timeout, Google::Protobuf::ParseError => e
|
262
|
+
# FIXME: Error handling on unsubscribe
|
263
|
+
nats.unsubscribe(sub.sid)
|
264
|
+
raise e
|
265
|
+
end
|
266
|
+
|
267
|
+
unless response.error.empty?
|
268
|
+
# FIXME: Error handling on unsubscribe
|
269
|
+
nats.unsubscribe(sub.sid)
|
270
|
+
raise Error.new(response.error)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Capture ack inbox for the subscription
|
274
|
+
sub.ack_inbox = response.ackInbox.freeze
|
275
|
+
|
276
|
+
return sub
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Close wraps us the session with the NATS Streaming server
|
281
|
+
def close
|
282
|
+
req = STAN::Protocol::CloseRequest.new(clientID: @client_id)
|
283
|
+
raw = nats.request(@close_req_subject, req.to_proto)
|
284
|
+
|
285
|
+
resp = STAN::Protocol::CloseResponse.decode(raw.data)
|
286
|
+
unless resp.error.empty?
|
287
|
+
raise Error.new(resp.error)
|
288
|
+
end
|
289
|
+
|
290
|
+
# TODO: If connection to nats was borrowed then we should
|
291
|
+
# unsubscribe from all topics from STAN. If not borrowed
|
292
|
+
# and we own the connection, then we just close.
|
293
|
+
begin
|
294
|
+
# Remove all present subscriptions
|
295
|
+
@sub_map.each_pair do |_, sub|
|
296
|
+
nats.unsubscribe(sub.sid)
|
297
|
+
end
|
298
|
+
|
299
|
+
# Finally, remove the core subscriptions for STAN
|
300
|
+
nats.unsubscribe(@hb_inbox_sid)
|
301
|
+
nats.unsubscribe(@ack_subject_sid)
|
302
|
+
rescue => e
|
303
|
+
# TODO: Async error handling
|
304
|
+
ensure
|
305
|
+
if @borrowed_nats_connection
|
306
|
+
@nats = nil
|
307
|
+
else
|
308
|
+
@nats.close
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Ack takes a received message and publishes an ack manually
|
314
|
+
def ack(msg)
|
315
|
+
return unless msg.sub
|
316
|
+
msg.sub.synchronize do
|
317
|
+
ack_proto = STAN::Protocol::Ack.new({
|
318
|
+
subject: msg.proto.subject,
|
319
|
+
sequence: msg.proto.sequence
|
320
|
+
}).to_proto
|
321
|
+
nats.publish(msg.sub.ack_inbox, ack_proto)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
# Process received publishes acks
|
328
|
+
def process_ack(data)
|
329
|
+
# FIXME: This should handle errors asynchronously in case there are any
|
330
|
+
|
331
|
+
# Process ack
|
332
|
+
pub_ack = STAN::Protocol::PubAck.decode(data)
|
333
|
+
unless pub_ack.error.empty?
|
334
|
+
raise Error.new(pub_ack.error)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Unblock publishing queue
|
338
|
+
@pending_pub_acks.pop if @pending_pub_acks.size > 0
|
339
|
+
|
340
|
+
synchronize do
|
341
|
+
# yield the ack response back to original publisher caller
|
342
|
+
if cb = @pub_ack_map[pub_ack.guid]
|
343
|
+
cb.call(pub_ack)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Process heartbeats by replying to them
|
349
|
+
def process_heartbeats(data, reply, subject)
|
350
|
+
# No payload assumed, just reply to the heartbeat.
|
351
|
+
nats.publish(reply, '')
|
352
|
+
end
|
353
|
+
|
354
|
+
# Process any received messages
|
355
|
+
def process_msg(data, reply, subject)
|
356
|
+
msg = Msg.new
|
357
|
+
msg.proto = STAN::Protocol::MsgProto.decode(data)
|
358
|
+
msg_ack = STAN::Protocol::Ack.new({
|
359
|
+
subject: msg.proto.subject,
|
360
|
+
sequence: msg.proto.sequence
|
361
|
+
})
|
362
|
+
|
363
|
+
# Lookup the subscription
|
364
|
+
sub = nil
|
365
|
+
synchronize do
|
366
|
+
sub = @sub_map[subject]
|
367
|
+
end
|
368
|
+
# Check if sub is no longer valid
|
369
|
+
return unless sub
|
370
|
+
|
371
|
+
# Store in msg for backlink
|
372
|
+
msg.sub = sub
|
373
|
+
|
374
|
+
cb = nil
|
375
|
+
ack_subject = nil
|
376
|
+
using_manual_acks = nil
|
377
|
+
sub.synchronize do
|
378
|
+
cb = sub.cb
|
379
|
+
ack_subject = sub.ack_inbox
|
380
|
+
using_manual_acks = sub.options[:manual_acks]
|
381
|
+
end
|
382
|
+
|
383
|
+
# Perform the callback if sub still subscribed
|
384
|
+
cb.call(msg) if cb
|
385
|
+
|
386
|
+
# Process auto-ack if not done manually
|
387
|
+
nats.publish(ack_subject, msg_ack.to_proto) if not using_manual_acks
|
388
|
+
end
|
389
|
+
|
390
|
+
def normalize_sub_req_params(opts)
|
391
|
+
sub_opts = {}
|
392
|
+
sub_opts[:qGroup] = opts[:queue] if opts[:queue]
|
393
|
+
sub_opts[:durableName] = opts[:durable_name] if opts[:durable_name]
|
394
|
+
|
395
|
+
sub_opts[:clientID] = @client_id
|
396
|
+
sub_opts[:maxInFlight] = opts[:max_inflight]
|
397
|
+
sub_opts[:ackWaitInSecs] = opts[:ack_wait] || opts[:ack_timeout]
|
398
|
+
|
399
|
+
# TODO: Error checking when all combinations of options are not declared
|
400
|
+
case opts[:start_at]
|
401
|
+
when :new_only
|
402
|
+
# By default, it already acts as :new_only which is
|
403
|
+
# without no initial replay, similar to bare NATS,
|
404
|
+
# but we allow setting it explicitly anyway.
|
405
|
+
sub_opts[:startPosition] = :NewOnly
|
406
|
+
when :last_received
|
407
|
+
sub_opts[:startPosition] = :LastReceived
|
408
|
+
when :time, :timedelta
|
409
|
+
# If using timedelta, need to get current time in UnixNano format
|
410
|
+
# FIXME: Implement support for :ago option which uses time in human.
|
411
|
+
sub_opts[:startPosition] = :TimeDeltaStart
|
412
|
+
start_at_time = opts[:time] * 1_000_000_000
|
413
|
+
sub_opts[:startTimeDelta] = (Time.now.to_f * 1_000_000_000) - start_at_time
|
414
|
+
when :sequence
|
415
|
+
sub_opts[:startPosition] = :SequenceStart
|
416
|
+
sub_opts[:startSequence] = opts[:sequence] || 0
|
417
|
+
when :first, :beginning
|
418
|
+
sub_opts[:startPosition] = :First
|
419
|
+
else
|
420
|
+
sub_opts[:startPosition] = :First if opts[:deliver_all_available]
|
421
|
+
end
|
422
|
+
|
423
|
+
sub_opts
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
class Subscription
|
428
|
+
attr_reader :subject, :queue, :inbox, :options, :cb, :durable_name, :stan
|
429
|
+
attr_accessor :sid, :ack_inbox
|
430
|
+
|
431
|
+
def initialize(subject, opts={}, cb)
|
432
|
+
@subject = subject
|
433
|
+
@queue = opts[:queue]
|
434
|
+
@inbox = STAN.create_inbox
|
435
|
+
@sid = nil # inbox subscription sid
|
436
|
+
@options = opts
|
437
|
+
@cb = cb
|
438
|
+
@ack_inbox = nil
|
439
|
+
@stan = opts[:stan]
|
440
|
+
@durable_name = opts[:durable_name]
|
441
|
+
end
|
442
|
+
|
443
|
+
# Unsubscribe removes interest in the subscription.
|
444
|
+
# For durables, it means that the durable interest is also removed from
|
445
|
+
# the server. Restarting a durable with the same name will not resume
|
446
|
+
# the subscription, it will be considered a new one.
|
447
|
+
def unsubscribe
|
448
|
+
synchronize do
|
449
|
+
stan.nats.unsubscribe(self.sid)
|
450
|
+
end
|
451
|
+
|
452
|
+
# Make client stop tracking the subscription inbox
|
453
|
+
# and grab unsub request subject under the lock.
|
454
|
+
unsub_subject = nil
|
455
|
+
stan.synchronize do
|
456
|
+
stan.sub_map.delete(self.ack_inbox)
|
457
|
+
unsub_subject = stan.unsub_req_subject
|
458
|
+
end
|
459
|
+
|
460
|
+
unsub_req = STAN::Protocol::UnsubscribeRequest.new({
|
461
|
+
clientID: stan.client_id,
|
462
|
+
subject: self.subject,
|
463
|
+
inbox: self.ack_inbox
|
464
|
+
})
|
465
|
+
|
466
|
+
if self.durable_name
|
467
|
+
unsub_req[:durableName] = self.durable_name
|
468
|
+
end
|
469
|
+
|
470
|
+
raw = stan.nats.request(unsub_subject, unsub_req.to_proto, {
|
471
|
+
timeout: stan.options[:connect_timeout]
|
472
|
+
})
|
473
|
+
response = STAN::Protocol::SubscriptionResponse.decode(raw.data)
|
474
|
+
unless response.error.empty?
|
475
|
+
# FIXME: Error handling on unsubscribe
|
476
|
+
raise Error.new(response.error)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def close
|
481
|
+
synchronize do
|
482
|
+
stan.nats.unsubscribe(self.sid)
|
483
|
+
end
|
484
|
+
|
485
|
+
# Make client stop tracking the subscription inbox
|
486
|
+
# and grab close request subject under the lock.
|
487
|
+
sub_close_subject = nil
|
488
|
+
stan.synchronize do
|
489
|
+
stan.sub_map.delete(self.ack_inbox)
|
490
|
+
sub_close_subject = stan.sub_close_req_subject
|
491
|
+
end
|
492
|
+
|
493
|
+
sub_close_req = STAN::Protocol::UnsubscribeRequest.new({
|
494
|
+
clientID: stan.client_id,
|
495
|
+
subject: self.subject,
|
496
|
+
inbox: self.ack_inbox
|
497
|
+
})
|
498
|
+
|
499
|
+
raw = stan.nats.request(sub_close_subject, sub_close_req.to_proto, {
|
500
|
+
timeout: stan.options[:connect_timeout]
|
501
|
+
})
|
502
|
+
response = STAN::Protocol::SubscriptionResponse.decode(raw.data)
|
503
|
+
unless response.error.empty?
|
504
|
+
# FIXME: Error handling on unsubscribe/close
|
505
|
+
raise Error.new(response.error)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
# Data holder for sent messages
|
511
|
+
# It should have an Ack method as well to reply back?
|
512
|
+
Msg = Struct.new(:proto, :sub) do
|
513
|
+
def data
|
514
|
+
self.proto.data
|
515
|
+
end
|
516
|
+
def sequence
|
517
|
+
self.proto.sequence
|
518
|
+
end
|
519
|
+
alias seq sequence
|
520
|
+
end
|
521
|
+
|
522
|
+
class << self
|
523
|
+
def create_guid
|
524
|
+
SecureRandom.hex(11)
|
525
|
+
end
|
526
|
+
|
527
|
+
def create_inbox
|
528
|
+
SecureRandom.hex(13)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: pb/protocol.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_message "STAN.Protocol.PubMsg" do
|
8
|
+
optional :clientID, :string, 1
|
9
|
+
optional :guid, :string, 2
|
10
|
+
optional :subject, :string, 3
|
11
|
+
optional :reply, :string, 4
|
12
|
+
optional :data, :bytes, 5
|
13
|
+
optional :sha256, :bytes, 10
|
14
|
+
end
|
15
|
+
add_message "STAN.Protocol.PubAck" do
|
16
|
+
optional :guid, :string, 1
|
17
|
+
optional :error, :string, 2
|
18
|
+
end
|
19
|
+
add_message "STAN.Protocol.MsgProto" do
|
20
|
+
optional :sequence, :uint64, 1
|
21
|
+
optional :subject, :string, 2
|
22
|
+
optional :reply, :string, 3
|
23
|
+
optional :data, :bytes, 4
|
24
|
+
optional :timestamp, :int64, 5
|
25
|
+
optional :redelivered, :bool, 6
|
26
|
+
optional :CRC32, :uint32, 10
|
27
|
+
end
|
28
|
+
add_message "STAN.Protocol.Ack" do
|
29
|
+
optional :subject, :string, 1
|
30
|
+
optional :sequence, :uint64, 2
|
31
|
+
end
|
32
|
+
add_message "STAN.Protocol.ConnectRequest" do
|
33
|
+
optional :clientID, :string, 1
|
34
|
+
optional :heartbeatInbox, :string, 2
|
35
|
+
end
|
36
|
+
add_message "STAN.Protocol.ConnectResponse" do
|
37
|
+
optional :pubPrefix, :string, 1
|
38
|
+
optional :subRequests, :string, 2
|
39
|
+
optional :unsubRequests, :string, 3
|
40
|
+
optional :closeRequests, :string, 4
|
41
|
+
optional :error, :string, 5
|
42
|
+
optional :subCloseRequests, :string, 6
|
43
|
+
optional :publicKey, :string, 100
|
44
|
+
end
|
45
|
+
add_message "STAN.Protocol.SubscriptionRequest" do
|
46
|
+
optional :clientID, :string, 1
|
47
|
+
optional :subject, :string, 2
|
48
|
+
optional :qGroup, :string, 3
|
49
|
+
optional :inbox, :string, 4
|
50
|
+
optional :maxInFlight, :int32, 5
|
51
|
+
optional :ackWaitInSecs, :int32, 6
|
52
|
+
optional :durableName, :string, 7
|
53
|
+
optional :startPosition, :enum, 10, "STAN.Protocol.StartPosition"
|
54
|
+
optional :startSequence, :uint64, 11
|
55
|
+
optional :startTimeDelta, :int64, 12
|
56
|
+
end
|
57
|
+
add_message "STAN.Protocol.SubscriptionResponse" do
|
58
|
+
optional :ackInbox, :string, 2
|
59
|
+
optional :error, :string, 3
|
60
|
+
end
|
61
|
+
add_message "STAN.Protocol.UnsubscribeRequest" do
|
62
|
+
optional :clientID, :string, 1
|
63
|
+
optional :subject, :string, 2
|
64
|
+
optional :inbox, :string, 3
|
65
|
+
optional :durableName, :string, 4
|
66
|
+
end
|
67
|
+
add_message "STAN.Protocol.CloseRequest" do
|
68
|
+
optional :clientID, :string, 1
|
69
|
+
end
|
70
|
+
add_message "STAN.Protocol.CloseResponse" do
|
71
|
+
optional :error, :string, 1
|
72
|
+
end
|
73
|
+
add_enum "STAN.Protocol.StartPosition" do
|
74
|
+
value :NewOnly, 0
|
75
|
+
value :LastReceived, 1
|
76
|
+
value :TimeDeltaStart, 2
|
77
|
+
value :SequenceStart, 3
|
78
|
+
value :First, 4
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module STAN
|
83
|
+
module Protocol
|
84
|
+
PubMsg = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.PubMsg").msgclass
|
85
|
+
PubAck = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.PubAck").msgclass
|
86
|
+
MsgProto = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.MsgProto").msgclass
|
87
|
+
Ack = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.Ack").msgclass
|
88
|
+
ConnectRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.ConnectRequest").msgclass
|
89
|
+
ConnectResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.ConnectResponse").msgclass
|
90
|
+
SubscriptionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.SubscriptionRequest").msgclass
|
91
|
+
SubscriptionResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.SubscriptionResponse").msgclass
|
92
|
+
UnsubscribeRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.UnsubscribeRequest").msgclass
|
93
|
+
CloseRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.CloseRequest").msgclass
|
94
|
+
CloseResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.CloseResponse").msgclass
|
95
|
+
StartPosition = Google::Protobuf::DescriptorPool.generated_pool.lookup("STAN.Protocol.StartPosition").enummodule
|
96
|
+
end
|
97
|
+
end
|
data/lib/stan/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nats-streaming
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Waldemar Quevedo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nats-pure
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: google-protobuf
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Ruby client for the NATS Streaming messaging system.
|
42
|
+
email:
|
43
|
+
- wally@apcera.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/stan/client.rb
|
49
|
+
- lib/stan/pb/protocol.pb.rb
|
50
|
+
- lib/stan/version.rb
|
51
|
+
homepage: https://nats.io
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.5.2
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: Ruby client for the NATS Streaming messaging system.
|
75
|
+
test_files: []
|