nats-pure 2.4.0 → 2.5.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/CHANGELOG.md +3 -0
- data/README.md +10 -3
- data/lib/nats/client.rb +7 -3
- data/lib/nats/io/client.rb +303 -280
- data/lib/nats/io/errors.rb +2 -0
- data/lib/nats/io/jetstream/api.rb +53 -50
- data/lib/nats/io/jetstream/errors.rb +30 -14
- data/lib/nats/io/jetstream/js/config.rb +9 -3
- data/lib/nats/io/jetstream/js/header.rb +15 -9
- data/lib/nats/io/jetstream/js/status.rb +11 -5
- data/lib/nats/io/jetstream/js/sub.rb +4 -2
- data/lib/nats/io/jetstream/js.rb +10 -8
- data/lib/nats/io/jetstream/manager.rb +103 -101
- data/lib/nats/io/jetstream/msg/ack.rb +15 -9
- data/lib/nats/io/jetstream/msg/ack_methods.rb +24 -22
- data/lib/nats/io/jetstream/msg/metadata.rb +9 -7
- data/lib/nats/io/jetstream/msg.rb +11 -4
- data/lib/nats/io/jetstream/pull_subscription.rb +21 -10
- data/lib/nats/io/jetstream/push_subscription.rb +3 -1
- data/lib/nats/io/jetstream.rb +102 -106
- data/lib/nats/io/kv/api.rb +7 -3
- data/lib/nats/io/kv/bucket_status.rb +7 -5
- data/lib/nats/io/kv/errors.rb +25 -2
- data/lib/nats/io/kv/manager.rb +19 -10
- data/lib/nats/io/kv.rb +359 -22
- data/lib/nats/io/msg.rb +19 -19
- data/lib/nats/io/parser.rb +23 -23
- data/lib/nats/io/rails.rb +2 -0
- data/lib/nats/io/subscription.rb +25 -22
- data/lib/nats/io/version.rb +4 -2
- data/lib/nats/io/websocket.rb +10 -8
- data/lib/nats/nuid.rb +33 -22
- data/lib/nats/service/callbacks.rb +22 -0
- data/lib/nats/service/endpoint.rb +155 -0
- data/lib/nats/service/errors.rb +44 -0
- data/lib/nats/service/group.rb +37 -0
- data/lib/nats/service/monitoring.rb +108 -0
- data/lib/nats/service/stats.rb +52 -0
- data/lib/nats/service/status.rb +66 -0
- data/lib/nats/service/validator.rb +31 -0
- data/lib/nats/service.rb +121 -0
- data/lib/nats/utils/list.rb +26 -0
- data/lib/nats-pure.rb +5 -0
- data/lib/nats.rb +10 -6
- metadata +176 -5
data/lib/nats/io/kv.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021-2025 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
4
6
|
# You may obtain a copy of the License at
|
@@ -12,13 +14,15 @@
|
|
12
14
|
# limitations under the License.
|
13
15
|
#
|
14
16
|
|
15
|
-
require_relative
|
16
|
-
require_relative
|
17
|
-
require_relative
|
18
|
-
require_relative
|
17
|
+
require_relative "kv/api"
|
18
|
+
require_relative "kv/bucket_status"
|
19
|
+
require_relative "kv/errors"
|
20
|
+
require_relative "kv/manager"
|
19
21
|
|
20
22
|
module NATS
|
21
23
|
class KeyValue
|
24
|
+
include MonitorMixin
|
25
|
+
|
22
26
|
KV_OP = "KV-Operation"
|
23
27
|
KV_DEL = "DEL"
|
24
28
|
KV_PURGE = "PURGE"
|
@@ -26,16 +30,35 @@ module NATS
|
|
26
30
|
MSG_ROLLUP_ALL = "all"
|
27
31
|
ROLLUP = "Nats-Rollup"
|
28
32
|
|
29
|
-
|
33
|
+
VALID_BUCKET_RE = /\A[a-zA-Z0-9_-]+$/
|
34
|
+
VALID_KEY_RE = /\A[-\/_=\.a-zA-Z0-9]+$/
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def is_valid_key(key)
|
38
|
+
if key.nil?
|
39
|
+
false
|
40
|
+
elsif key.start_with?(".") || key.end_with?(".")
|
41
|
+
false
|
42
|
+
elsif key !~ VALID_KEY_RE
|
43
|
+
false
|
44
|
+
else
|
45
|
+
true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(opts = {})
|
30
51
|
@name = opts[:name]
|
31
52
|
@stream = opts[:stream]
|
32
53
|
@pre = opts[:pre]
|
33
54
|
@js = opts[:js]
|
34
55
|
@direct = opts[:direct]
|
56
|
+
@validate_keys = opts[:validate_keys]
|
35
57
|
end
|
36
58
|
|
37
59
|
# get returns the latest value for the key.
|
38
|
-
def get(key, params={})
|
60
|
+
def get(key, params = {})
|
61
|
+
raise InvalidKeyError if @validate_keys && !KeyValue.is_valid_key(key)
|
39
62
|
entry = nil
|
40
63
|
begin
|
41
64
|
entry = _get(key, params)
|
@@ -46,19 +69,19 @@ module NATS
|
|
46
69
|
entry
|
47
70
|
end
|
48
71
|
|
49
|
-
def _get(key, params={})
|
72
|
+
def _get(key, params = {})
|
50
73
|
msg = nil
|
51
74
|
subject = "#{@pre}#{key}"
|
52
75
|
|
53
|
-
if params[:revision]
|
54
|
-
|
55
|
-
|
56
|
-
|
76
|
+
msg = if params[:revision]
|
77
|
+
@js.get_msg(@stream,
|
78
|
+
seq: params[:revision],
|
79
|
+
direct: @direct)
|
57
80
|
else
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
81
|
+
@js.get_msg(@stream,
|
82
|
+
subject: subject,
|
83
|
+
seq: params[:revision],
|
84
|
+
direct: @direct)
|
62
85
|
end
|
63
86
|
|
64
87
|
entry = Entry.new(bucket: @name, key: key, value: msg.data, revision: msg.seq)
|
@@ -70,9 +93,9 @@ module NATS
|
|
70
93
|
)
|
71
94
|
end
|
72
95
|
|
73
|
-
if
|
96
|
+
if !msg.headers.nil?
|
74
97
|
op = msg.headers[KV_OP]
|
75
|
-
if op == KV_DEL
|
98
|
+
if (op == KV_DEL) || (op == KV_PURGE)
|
76
99
|
raise KeyDeletedError.new(entry: entry, op: op)
|
77
100
|
end
|
78
101
|
end
|
@@ -86,12 +109,16 @@ module NATS
|
|
86
109
|
# put will place the new value for the key into the store
|
87
110
|
# and return the revision number.
|
88
111
|
def put(key, value)
|
112
|
+
raise InvalidKeyError if @validate_keys && !KeyValue.is_valid_key(key)
|
113
|
+
|
89
114
|
ack = @js.publish("#{@pre}#{key}", value)
|
90
115
|
ack.seq
|
91
116
|
end
|
92
117
|
|
93
118
|
# create will add the key/value pair iff it does not exist.
|
94
119
|
def create(key, value)
|
120
|
+
raise InvalidKeyError if @validate_keys && !KeyValue.is_valid_key(key)
|
121
|
+
|
95
122
|
pa = nil
|
96
123
|
begin
|
97
124
|
pa = update(key, value, last: 0)
|
@@ -122,7 +149,9 @@ module NATS
|
|
122
149
|
EXPECTED_LAST_SUBJECT_SEQUENCE = "Nats-Expected-Last-Subject-Sequence"
|
123
150
|
|
124
151
|
# update will update the value iff the latest revision matches.
|
125
|
-
def update(key, value, params={})
|
152
|
+
def update(key, value, params = {})
|
153
|
+
raise InvalidKeyError if @validate_keys && !KeyValue.is_valid_key(key)
|
154
|
+
|
126
155
|
hdrs = {}
|
127
156
|
last = (params[:last] ||= 0)
|
128
157
|
hdrs[EXPECTED_LAST_SUBJECT_SEQUENCE] = last.to_s
|
@@ -141,7 +170,9 @@ module NATS
|
|
141
170
|
end
|
142
171
|
|
143
172
|
# delete will place a delete marker and remove all previous revisions.
|
144
|
-
def delete(key, params={})
|
173
|
+
def delete(key, params = {})
|
174
|
+
raise InvalidKeyError if @validate_keys && !KeyValue.is_valid_key(key)
|
175
|
+
|
145
176
|
hdrs = {}
|
146
177
|
hdrs[KV_OP] = KV_DEL
|
147
178
|
last = (params[:last] ||= 0)
|
@@ -155,6 +186,8 @@ module NATS
|
|
155
186
|
|
156
187
|
# purge will remove the key and all revisions.
|
157
188
|
def purge(key)
|
189
|
+
raise InvalidKeyError if @validate_keys && !KeyValue.is_valid_key(key)
|
190
|
+
|
158
191
|
hdrs = {}
|
159
192
|
hdrs[KV_OP] = KV_PURGE
|
160
193
|
hdrs[ROLLUP] = MSG_ROLLUP_SUBJECT
|
@@ -168,11 +201,315 @@ module NATS
|
|
168
201
|
end
|
169
202
|
|
170
203
|
Entry = Struct.new(:bucket, :key, :value, :revision, :delta, :created, :operation, keyword_init: true) do
|
171
|
-
def initialize(opts={})
|
204
|
+
def initialize(opts = {})
|
172
205
|
rem = opts.keys - members
|
173
206
|
opts.delete_if { |k| rem.include?(k) }
|
174
|
-
super
|
207
|
+
super
|
175
208
|
end
|
176
209
|
end
|
210
|
+
|
211
|
+
# watch will be signaled when any key is updated.
|
212
|
+
def watchall(params = {})
|
213
|
+
watch(">", params)
|
214
|
+
end
|
215
|
+
|
216
|
+
# keys returns the keys from a KeyValue store.
|
217
|
+
# Optionally filters the keys based on the provided filter list.
|
218
|
+
def keys(params = {})
|
219
|
+
params[:ignore_deletes] = true
|
220
|
+
params[:meta_only] = true
|
221
|
+
|
222
|
+
w = watchall(params)
|
223
|
+
got_keys = false
|
224
|
+
|
225
|
+
Enumerator.new do |y|
|
226
|
+
w.each do |entry|
|
227
|
+
break if entry.nil?
|
228
|
+
got_keys = true
|
229
|
+
y << entry.key
|
230
|
+
end
|
231
|
+
w.stop
|
232
|
+
raise NoKeysFoundError unless got_keys
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# history retrieves the entries so far for a key.
|
237
|
+
def history(key, params = {})
|
238
|
+
params[:include_history] = true
|
239
|
+
w = watch(key, params)
|
240
|
+
got_keys = false
|
241
|
+
|
242
|
+
Enumerator.new do |y|
|
243
|
+
w.each do |entry|
|
244
|
+
break if entry.nil?
|
245
|
+
got_keys = true
|
246
|
+
y << entry
|
247
|
+
end
|
248
|
+
w.stop
|
249
|
+
raise NoKeysFoundError unless got_keys
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
STATUS_HDR = "Status"
|
254
|
+
DESC_HDR = "Description"
|
255
|
+
CTRL_STATUS = "100"
|
256
|
+
LAST_CONSUMER_SEQ_HDR = "Nats-Last-Consumer"
|
257
|
+
LAST_STREAM_SEQ_HDR = "Nats-Last-Stream"
|
258
|
+
CONSUMER_STALLED_HDR = "Nats-Consumer-Stalled"
|
259
|
+
|
260
|
+
# watch will be signaled when a key that matches the keys
|
261
|
+
# pattern is updated.
|
262
|
+
# The first update after starting the watch is nil in case
|
263
|
+
# there are no pending updates.
|
264
|
+
def watch(keys, params = {})
|
265
|
+
params[:meta_only] ||= false
|
266
|
+
params[:include_history] ||= false
|
267
|
+
params[:ignore_deletes] ||= false
|
268
|
+
params[:idle_heartbeat] ||= 5 # seconds
|
269
|
+
params[:inactive_threshold] ||= 5 * 60 # 5 minutes
|
270
|
+
subject = "#{@pre}#{keys}"
|
271
|
+
init_setup = new_cond
|
272
|
+
init_setup_done = false
|
273
|
+
nc = @js.nc
|
274
|
+
watcher = KeyWatcher.new(@js)
|
275
|
+
|
276
|
+
deliver_policy = if !(params[:include_history])
|
277
|
+
"last_per_subject"
|
278
|
+
end
|
279
|
+
|
280
|
+
ordered = {
|
281
|
+
# basic ordered consumer.
|
282
|
+
flow_control: true,
|
283
|
+
ack_policy: "none",
|
284
|
+
max_deliver: 1,
|
285
|
+
ack_wait: 22 * 3600,
|
286
|
+
idle_heartbeat: params[:idle_heartbeat],
|
287
|
+
num_replicas: 1,
|
288
|
+
mem_storage: true,
|
289
|
+
manual_ack: true,
|
290
|
+
# watch related options.
|
291
|
+
deliver_policy: deliver_policy,
|
292
|
+
headers_only: params[:meta_only],
|
293
|
+
inactive_threshold: params[:inactive_threshold]
|
294
|
+
}
|
295
|
+
|
296
|
+
# watch_updates callback.
|
297
|
+
sub = @js.subscribe(subject, config: ordered) do |msg|
|
298
|
+
synchronize do
|
299
|
+
if !init_setup_done
|
300
|
+
init_setup.wait(@js.opts[:timeout])
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Control Message like Heartbeats and Flow Control
|
305
|
+
status = msg.header[STATUS_HDR] unless msg.header.nil?
|
306
|
+
if !status.nil? && status == CTRL_STATUS
|
307
|
+
desc = msg.header[DESC_HDR]
|
308
|
+
if desc.start_with?("Idle")
|
309
|
+
# A watcher is active if it continues to receive Idle Heartbeat messages.
|
310
|
+
#
|
311
|
+
# Status: 100
|
312
|
+
# Description: Idle Heartbeat
|
313
|
+
# Nats-Last-Consumer: 185
|
314
|
+
# Nats-Last-Stream: 185
|
315
|
+
#
|
316
|
+
watcher.synchronize { watcher._active = true }
|
317
|
+
elsif desc.start_with?("FlowControl")
|
318
|
+
# HMSG _INBOX.q6Y3JAFxOnNJi4QdwQnFtg 2 $JS.FC.KV_TEST.t00CunIG.GT4W 36 36
|
319
|
+
# NATS/1.0 100 FlowControl Request
|
320
|
+
nc.publish(msg.reply)
|
321
|
+
end
|
322
|
+
# Skip processing the control message
|
323
|
+
next
|
324
|
+
end
|
325
|
+
|
326
|
+
# Track sequences
|
327
|
+
meta = msg.metadata
|
328
|
+
watcher.synchronize { watcher._active = true }
|
329
|
+
# Track the sequences
|
330
|
+
#
|
331
|
+
# $JS.ACK.KV_TEST.CKRGrWpf.1.10.10.1739859923871837000.0
|
332
|
+
#
|
333
|
+
tokens = msg.reply.split(".")
|
334
|
+
sseq = tokens[5]
|
335
|
+
dseq = tokens[6]
|
336
|
+
watcher.synchronize do
|
337
|
+
watcher._dseq = dseq.to_i + 1
|
338
|
+
watcher._sseq = sseq.to_i
|
339
|
+
end
|
340
|
+
|
341
|
+
# Keys() handling
|
342
|
+
op = nil
|
343
|
+
if msg.header && msg.header[KV_OP]
|
344
|
+
op = msg.header[KV_OP]
|
345
|
+
if params[:ignore_deletes]
|
346
|
+
if (op == KV_PURGE) || (op == KV_DEL)
|
347
|
+
if (meta.num_pending == 0) && !watcher._init_done
|
348
|
+
# Push this to unblock enumerators.
|
349
|
+
watcher._updates.push(nil)
|
350
|
+
watcher._init_done = true
|
351
|
+
end
|
352
|
+
next
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Convert the msg into an Entry.
|
358
|
+
key = msg.subject[@pre.size...msg.subject.size]
|
359
|
+
entry = Entry.new(
|
360
|
+
bucket: @name,
|
361
|
+
key: key,
|
362
|
+
value: msg.data,
|
363
|
+
revision: meta.sequence.stream,
|
364
|
+
delta: meta.num_pending,
|
365
|
+
created: meta.timestamp,
|
366
|
+
operation: op
|
367
|
+
)
|
368
|
+
watcher._updates.push(entry)
|
369
|
+
|
370
|
+
# When there are no more updates send an empty marker
|
371
|
+
# to signal that it is done, this will unblock iterators.
|
372
|
+
if (meta.num_pending == 0) && !watcher._init_done
|
373
|
+
watcher._updates.push(nil)
|
374
|
+
watcher._init_done = true
|
375
|
+
end
|
376
|
+
end # end of callback
|
377
|
+
watcher._sub = sub
|
378
|
+
|
379
|
+
# Snapshot the deliver subject for the consumer.
|
380
|
+
deliver_subject = sub.subject
|
381
|
+
|
382
|
+
# Check from consumer info what is the number of messages
|
383
|
+
# awaiting to be consumed to send the initial signal marker.
|
384
|
+
stream_name = nil
|
385
|
+
begin
|
386
|
+
cinfo = sub.consumer_info
|
387
|
+
stream_name = cinfo.stream_name
|
388
|
+
|
389
|
+
synchronize do
|
390
|
+
init_setup_done = true
|
391
|
+
# If no delivered and/or pending messages, then signal
|
392
|
+
# that this is the start.
|
393
|
+
# The consumer subscription will start receiving messages
|
394
|
+
# so need to check those that have already made it.
|
395
|
+
received = sub.delivered
|
396
|
+
init_setup.signal
|
397
|
+
|
398
|
+
# When there are no more updates send an empty marker
|
399
|
+
# to signal that it is done, this will unblock iterators.
|
400
|
+
if (cinfo.num_pending == 0) && (received == 0)
|
401
|
+
watcher._updates.push(nil)
|
402
|
+
watcher._init_done = true
|
403
|
+
end
|
404
|
+
end
|
405
|
+
rescue => err
|
406
|
+
# cancel init
|
407
|
+
sub.unsubscribe
|
408
|
+
raise err
|
409
|
+
end
|
410
|
+
|
411
|
+
# Need to handle reconnect if missing too many heartbeats.
|
412
|
+
hb_interval = params[:idle_heartbeat] * 2
|
413
|
+
watcher._hb_task = Concurrent::TimerTask.new(execution_interval: hb_interval) do |task|
|
414
|
+
task.shutdown if nc.closed?
|
415
|
+
next unless nc.connected?
|
416
|
+
|
417
|
+
# Wait for all idle heartbeats to be received, one of them would have
|
418
|
+
# toggled the state of the consumer back to being active.
|
419
|
+
active = watcher.synchronize {
|
420
|
+
current = watcher._active
|
421
|
+
# A heartbeat or another incoming message needs to toggle back.
|
422
|
+
watcher._active = false
|
423
|
+
current
|
424
|
+
}
|
425
|
+
if !active
|
426
|
+
ccreq = ordered.dup
|
427
|
+
ccreq[:deliver_policy] = "by_start_sequence"
|
428
|
+
ccreq[:opt_start_seq] = watcher._sseq
|
429
|
+
ccreq[:deliver_subject] = deliver_subject
|
430
|
+
ccreq[:idle_heartbeat] = ordered[:idle_heartbeat]
|
431
|
+
ccreq[:inactive_threshold] = ordered[:inactive_threshold]
|
432
|
+
|
433
|
+
should_recreate = false
|
434
|
+
begin
|
435
|
+
# Check if the original is still present, if it is then do not recreate.
|
436
|
+
begin
|
437
|
+
sub.consumer_info
|
438
|
+
rescue ::NATS::JetStream::Error::ConsumerNotFound => e
|
439
|
+
e.stream ||= sub.jsi.stream
|
440
|
+
e.consumer ||= sub.jsi.consumer
|
441
|
+
@js.nc.send(:err_cb_call, @js.nc, e, sub)
|
442
|
+
should_recreate = true
|
443
|
+
end
|
444
|
+
next unless should_recreate
|
445
|
+
|
446
|
+
# Recreate consumer that went away after a restart.
|
447
|
+
cinfo = @js.add_consumer(stream_name, ccreq)
|
448
|
+
sub.jsi.consumer = cinfo.name
|
449
|
+
watcher.synchronize { watcher._dseq = 1 }
|
450
|
+
rescue => e
|
451
|
+
# Dispatch to the error NATS client error callback.
|
452
|
+
@js.nc.send(:err_cb_call, @js.nc, e, sub)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
rescue => e
|
456
|
+
# WRN: Unexpected error
|
457
|
+
@js.nc.send(:err_cb_call, @js.nc, e, sub)
|
458
|
+
end
|
459
|
+
watcher._hb_task.execute
|
460
|
+
|
461
|
+
watcher
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
class KeyWatcher
|
466
|
+
include MonitorMixin
|
467
|
+
include Enumerable
|
468
|
+
attr_accessor :received, :pending, :_sub, :_updates, :_init_done, :_watcher_cond
|
469
|
+
attr_accessor :_sseq, :_dseq, :_active, :_hb_task
|
470
|
+
|
471
|
+
def initialize(js)
|
472
|
+
super() # required to initialize monitor
|
473
|
+
@js = js
|
474
|
+
@_sub = nil
|
475
|
+
@_updates = SizedQueue.new(256)
|
476
|
+
@_init_done = false
|
477
|
+
@pending = nil
|
478
|
+
# Ordered consumer related
|
479
|
+
@_dseq = 1
|
480
|
+
@_sseq = 0
|
481
|
+
@_cmeta = nil
|
482
|
+
@_fcr = 0
|
483
|
+
@_fciseq = 0
|
484
|
+
@_active = true
|
485
|
+
@_hb_task = nil
|
486
|
+
end
|
487
|
+
|
488
|
+
def stop
|
489
|
+
@_hb_task.shutdown
|
490
|
+
@_sub.unsubscribe
|
491
|
+
end
|
492
|
+
|
493
|
+
def updates(params = {})
|
494
|
+
params[:timeout] ||= 5
|
495
|
+
result = nil
|
496
|
+
MonotonicTime.with_nats_timeout(params[:timeout]) do
|
497
|
+
result = @_updates.pop(timeout: params[:timeout])
|
498
|
+
end
|
499
|
+
|
500
|
+
result
|
501
|
+
end
|
502
|
+
|
503
|
+
# Implements Enumerable.
|
504
|
+
def each
|
505
|
+
loop do
|
506
|
+
result = @_updates.pop
|
507
|
+
yield result
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def take(n)
|
512
|
+
super.take(n).reject { |entry| entry.nil? }
|
513
|
+
end
|
177
514
|
end
|
178
515
|
end
|
data/lib/nats/io/msg.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright 2016-2021 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
@@ -10,36 +12,34 @@
|
|
10
12
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11
13
|
# See the License for the specific language governing permissions and
|
12
14
|
# limitations under the License.
|
13
|
-
#
|
14
|
-
require_relative 'jetstream'
|
15
15
|
|
16
16
|
module NATS
|
17
17
|
class Msg
|
18
18
|
attr_accessor :subject, :reply, :data, :header
|
19
|
+
attr_accessor :nc, :sub
|
19
20
|
|
20
|
-
|
21
|
-
include JetStream::Msg::AckMethods
|
22
|
-
|
23
|
-
def initialize(opts={})
|
21
|
+
def initialize(opts = {})
|
24
22
|
@subject = opts[:subject]
|
25
|
-
@reply
|
26
|
-
@data
|
27
|
-
@header
|
28
|
-
@nc
|
29
|
-
@sub
|
30
|
-
|
31
|
-
|
23
|
+
@reply = opts[:reply]
|
24
|
+
@data = opts[:data]
|
25
|
+
@header = opts[:header]
|
26
|
+
@nc = opts[:nc]
|
27
|
+
@sub = opts[:sub]
|
28
|
+
|
29
|
+
# JS related
|
30
|
+
@ackd = false
|
31
|
+
@meta = nil
|
32
32
|
end
|
33
33
|
|
34
|
-
def respond(data=
|
34
|
+
def respond(data = "")
|
35
35
|
return unless @nc
|
36
|
-
if
|
37
|
-
dmsg =
|
38
|
-
dmsg.subject =
|
36
|
+
if header
|
37
|
+
dmsg = dup
|
38
|
+
dmsg.subject = reply
|
39
39
|
dmsg.data = data
|
40
40
|
@nc.publish_msg(dmsg)
|
41
41
|
else
|
42
|
-
@nc.publish(
|
42
|
+
@nc.publish(reply, data)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -50,7 +50,7 @@ module NATS
|
|
50
50
|
|
51
51
|
def inspect
|
52
52
|
hdr = ", header=#{@header}" if @header
|
53
|
-
dot =
|
53
|
+
dot = "..." if @data.length > 10
|
54
54
|
dat = "#{data.slice(0, 10)}#{dot}"
|
55
55
|
"#<NATS::Msg(subject: \"#{@subject}\", reply: \"#{@reply}\", data: #{dat.inspect}#{hdr})>"
|
56
56
|
end
|
data/lib/nats/io/parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright 2016-2018 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
@@ -14,27 +16,26 @@
|
|
14
16
|
|
15
17
|
module NATS
|
16
18
|
module Protocol
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
UNKNOWN = /\A(.*)\r\n/
|
19
|
+
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
|
20
|
+
HMSG = /\AHMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([\d]+)\s+(\d+)\r\n/i
|
21
|
+
OK = /\A\+OK\s*\r\n/i
|
22
|
+
ERR = /\A-ERR\s+('.+')?\r\n/i
|
23
|
+
PING = /\APING\s*\r\n/i
|
24
|
+
PONG = /\APONG\s*\r\n/i
|
25
|
+
INFO = /\AINFO\s+([^\r\n]+)\r\n/i
|
26
|
+
UNKNOWN = /\A(.*)\r\n/
|
26
27
|
|
27
28
|
AWAITING_CONTROL_LINE = 1
|
28
|
-
AWAITING_MSG_PAYLOAD
|
29
|
+
AWAITING_MSG_PAYLOAD = 2
|
29
30
|
|
30
|
-
CR_LF =
|
31
|
-
CR_LF_SIZE =
|
31
|
+
CR_LF = "\r\n"
|
32
|
+
CR_LF_SIZE = CR_LF.bytesize
|
32
33
|
|
33
|
-
PING_REQUEST
|
34
|
-
PONG_RESPONSE =
|
34
|
+
PING_REQUEST = "PING#{CR_LF}".freeze
|
35
|
+
PONG_RESPONSE = "PONG#{CR_LF}".freeze
|
35
36
|
|
36
|
-
SUB_OP =
|
37
|
-
EMPTY_MSG =
|
37
|
+
SUB_OP = "SUB"
|
38
|
+
EMPTY_MSG = ""
|
38
39
|
|
39
40
|
class Parser
|
40
41
|
def initialize(nc)
|
@@ -55,7 +56,7 @@ module NATS
|
|
55
56
|
|
56
57
|
def parse(data)
|
57
58
|
@buf = @buf ? @buf << data : data
|
58
|
-
while
|
59
|
+
while @buf
|
59
60
|
case @parse_state
|
60
61
|
when AWAITING_CONTROL_LINE
|
61
62
|
case @buf
|
@@ -91,23 +92,22 @@ module NATS
|
|
91
92
|
# If we are here we do not have a complete line yet that we understand.
|
92
93
|
return
|
93
94
|
end
|
94
|
-
@buf = nil if
|
95
|
+
@buf = nil if @buf && @buf.empty?
|
95
96
|
|
96
97
|
when AWAITING_MSG_PAYLOAD
|
97
|
-
return unless
|
98
|
+
return unless @needed && @buf.bytesize >= (@needed + CR_LF_SIZE)
|
98
99
|
if @header_needed
|
99
100
|
hbuf = @buf.slice(0, @header_needed)
|
100
|
-
payload = @buf.slice(@header_needed, (@needed
|
101
|
+
payload = @buf.slice(@header_needed, (@needed - @header_needed))
|
101
102
|
@nc.send(:process_msg, @sub, @sid, @reply, payload, hbuf)
|
102
|
-
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
103
103
|
else
|
104
104
|
@nc.send(:process_msg, @sub, @sid, @reply, @buf.slice(0, @needed), nil)
|
105
|
-
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
106
105
|
end
|
106
|
+
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
107
107
|
|
108
108
|
@sub = @sid = @reply = @needed = @header_needed = nil
|
109
109
|
@parse_state = AWAITING_CONTROL_LINE
|
110
|
-
@buf = nil if
|
110
|
+
@buf = nil if @buf && @buf.empty?
|
111
111
|
end
|
112
112
|
end
|
113
113
|
end
|