nats-streaming 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|