nats-pure 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|