nats-pure 0.4.0 → 0.7.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/lib/nats/io/client.rb +459 -25
- data/lib/nats/io/parser.rb +31 -3
- data/lib/nats/io/version.rb +16 -2
- data/lib/nats/nuid.rb +81 -0
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d21f9a3f83d1aaa172f0835a2d74b5109a8e937429550981427b84325f97f1d3
|
4
|
+
data.tar.gz: 48ea0ef20a7c2611023573f883384d15ebf6bf9c342f37336b25157d79963098
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b72826e0d687cee2e303f3fae2abbc34ad6fe10ce35967d2201ecd3838c27f186e70c99c880288bfd2fbdc840edebfa80f4f31e282caafb15f899b41301a5a94
|
7
|
+
data.tar.gz: b5328d07ffa92de70d32cb37b28bf1c08db2866b58902dee2f761b6c0cf758c18037262fca4f42a3ab5bf6c4f0663abb733d8322cdfe8a2e30d8bd9de9a4139f
|
data/lib/nats/io/client.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
|
+
# Copyright 2016-2021 The NATS Authors
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
|
+
# you may not use this file except in compliance with the License.
|
4
|
+
# You may obtain a copy of the License at
|
5
|
+
#
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11
|
+
# See the License for the specific language governing permissions and
|
12
|
+
# limitations under the License.
|
13
|
+
#
|
14
|
+
|
1
15
|
require 'nats/io/parser'
|
2
16
|
require 'nats/io/version'
|
17
|
+
require 'nats/nuid'
|
3
18
|
require 'thread'
|
4
19
|
require 'socket'
|
5
20
|
require 'json'
|
@@ -49,6 +64,12 @@ module NATS
|
|
49
64
|
PING_REQUEST = ("PING#{CR_LF}".freeze)
|
50
65
|
PONG_RESPONSE = ("PONG#{CR_LF}".freeze)
|
51
66
|
|
67
|
+
NATS_HDR_LINE = ("NATS/1.0#{CR_LF}".freeze)
|
68
|
+
STATUS_MSG_LEN = 3
|
69
|
+
STATUS_HDR = ("Status".freeze)
|
70
|
+
DESC_HDR = ("Description".freeze)
|
71
|
+
NATS_HDR_LINE_SIZE = (NATS_HDR_LINE.bytesize)
|
72
|
+
|
52
73
|
SUB_OP = ('SUB'.freeze)
|
53
74
|
EMPTY_MSG = (''.freeze)
|
54
75
|
|
@@ -76,6 +97,9 @@ module NATS
|
|
76
97
|
# When we cannot connect since there are no servers available.
|
77
98
|
class NoServersError < ConnectError; end
|
78
99
|
|
100
|
+
# When there are no subscribers available to respond.
|
101
|
+
class NoRespondersError < ConnectError; end
|
102
|
+
|
79
103
|
# When the connection exhausts max number of pending pings replies.
|
80
104
|
class StaleConnectionError < Error; end
|
81
105
|
|
@@ -159,13 +183,44 @@ module NATS
|
|
159
183
|
# Hostname of current server; used for when TLS host
|
160
184
|
# verification is enabled.
|
161
185
|
@hostname = nil
|
186
|
+
@single_url_connect_used = false
|
187
|
+
|
188
|
+
# New style request/response implementation.
|
189
|
+
@resp_sub = nil
|
190
|
+
@resp_map = nil
|
191
|
+
@resp_sub_prefix = nil
|
192
|
+
@nuid = NATS::NUID.new
|
193
|
+
|
194
|
+
# NKEYS
|
195
|
+
@user_credentials = nil
|
196
|
+
@nkeys_seed = nil
|
197
|
+
@user_nkey_cb = nil
|
198
|
+
@user_jwt_cb = nil
|
199
|
+
@signature_cb = nil
|
162
200
|
end
|
163
201
|
|
164
|
-
# Establishes connection to NATS
|
165
|
-
def connect(opts={})
|
202
|
+
# Establishes connection to NATS.
|
203
|
+
def connect(uri=nil, opts={})
|
204
|
+
case uri
|
205
|
+
when String
|
206
|
+
# Initialize TLS defaults in case any url is using it.
|
207
|
+
srvs = opts[:servers] = process_uri(uri)
|
208
|
+
if srvs.any? {|u| u.scheme == 'tls'} and !opts[:tls]
|
209
|
+
tls_context = OpenSSL::SSL::SSLContext.new
|
210
|
+
tls_context.set_params
|
211
|
+
opts[:tls] = {
|
212
|
+
context: tls_context
|
213
|
+
}
|
214
|
+
end
|
215
|
+
@single_url_connect_used = true if srvs.size == 1
|
216
|
+
when Hash
|
217
|
+
opts = uri
|
218
|
+
end
|
219
|
+
|
166
220
|
opts[:verbose] = false if opts[:verbose].nil?
|
167
221
|
opts[:pedantic] = false if opts[:pedantic].nil?
|
168
222
|
opts[:reconnect] = true if opts[:reconnect].nil?
|
223
|
+
opts[:old_style_request] = false if opts[:old_style_request].nil?
|
169
224
|
opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
|
170
225
|
opts[:max_reconnect_attempts] = MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
|
171
226
|
opts[:ping_interval] = DEFAULT_PING_INTERVAL if opts[:ping_interval].nil?
|
@@ -198,6 +253,17 @@ module NATS
|
|
198
253
|
}
|
199
254
|
end
|
200
255
|
|
256
|
+
if @options[:old_style_request]
|
257
|
+
# Replace for this instance the implementation
|
258
|
+
# of request to use the old_request style.
|
259
|
+
class << self; alias_method :request, :old_request; end
|
260
|
+
end
|
261
|
+
|
262
|
+
# NKEYS
|
263
|
+
@user_credentials ||= opts[:user_credentials]
|
264
|
+
@nkeys_seed ||= opts[:nkeys_seed]
|
265
|
+
setup_nkeys_connect if @user_credentials or @nkeys_seed
|
266
|
+
|
201
267
|
# Check for TLS usage
|
202
268
|
@tls = @options[:tls]
|
203
269
|
|
@@ -217,7 +283,12 @@ module NATS
|
|
217
283
|
@status = CONNECTING
|
218
284
|
|
219
285
|
# Use the hostname from the server for TLS hostname verification.
|
220
|
-
|
286
|
+
if client_using_secure_connection? and single_url_connect_used?
|
287
|
+
# Always reuse the original hostname used to connect.
|
288
|
+
@hostname ||= srv[:hostname]
|
289
|
+
else
|
290
|
+
@hostname = srv[:hostname]
|
291
|
+
end
|
221
292
|
|
222
293
|
# Established TCP connection successfully so can start connect
|
223
294
|
process_connect_init
|
@@ -284,6 +355,36 @@ module NATS
|
|
284
355
|
@flush_queue << :pub if @flush_queue.empty?
|
285
356
|
end
|
286
357
|
|
358
|
+
# Publishes a NATS::Msg that may include headers.
|
359
|
+
def publish_msg(msg)
|
360
|
+
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
361
|
+
raise BadSubject if !msg.subject or msg.subject.empty?
|
362
|
+
|
363
|
+
msg.reply ||= ''
|
364
|
+
msg.data ||= ''
|
365
|
+
msg_size = msg.data.bytesize
|
366
|
+
|
367
|
+
# Accounting
|
368
|
+
@stats[:out_msgs] += 1
|
369
|
+
@stats[:out_bytes] += msg_size
|
370
|
+
|
371
|
+
if msg.header
|
372
|
+
hdr = ''
|
373
|
+
hdr << NATS_HDR_LINE
|
374
|
+
msg.header.each do |k, v|
|
375
|
+
hdr << "#{k}: #{v}#{CR_LF}"
|
376
|
+
end
|
377
|
+
hdr << CR_LF
|
378
|
+
hdr_len = hdr.bytesize
|
379
|
+
total_size = msg_size + hdr_len
|
380
|
+
send_command("HPUB #{msg.subject} #{msg.reply} #{hdr_len} #{total_size}\r\n#{hdr}#{msg.data}\r\n")
|
381
|
+
else
|
382
|
+
send_command("PUB #{msg.subject} #{msg.reply} #{msg_size}\r\n#{msg.data}\r\n")
|
383
|
+
end
|
384
|
+
|
385
|
+
@flush_queue << :pub if @flush_queue.empty?
|
386
|
+
end
|
387
|
+
|
287
388
|
# Create subscription which is dispatched asynchronously
|
288
389
|
# messages to a callback.
|
289
390
|
def subscribe(subject, opts={}, &callback)
|
@@ -331,7 +432,8 @@ module NATS
|
|
331
432
|
when 0 then cb.call
|
332
433
|
when 1 then cb.call(msg.data)
|
333
434
|
when 2 then cb.call(msg.data, msg.reply)
|
334
|
-
|
435
|
+
when 3 then cb.call(msg.data, msg.reply, msg.subject)
|
436
|
+
else cb.call(msg.data, msg.reply, msg.subject, msg.header)
|
335
437
|
end
|
336
438
|
rescue => e
|
337
439
|
synchronize do
|
@@ -344,10 +446,123 @@ module NATS
|
|
344
446
|
sid
|
345
447
|
end
|
346
448
|
|
347
|
-
# Sends a request expecting a single response
|
348
|
-
#
|
449
|
+
# Sends a request using expecting a single response using a
|
450
|
+
# single subscription per connection for receiving the responses.
|
451
|
+
# It times out in case the request is not retrieved within the
|
452
|
+
# specified deadline.
|
349
453
|
# If given a callback, then the request happens asynchronously.
|
350
|
-
def request(subject, payload, opts={}, &blk)
|
454
|
+
def request(subject, payload="", opts={}, &blk)
|
455
|
+
raise BadSubject if !subject or subject.empty?
|
456
|
+
|
457
|
+
# If a block was given then fallback to method using auto unsubscribe.
|
458
|
+
return old_request(subject, payload, opts, &blk) if blk
|
459
|
+
return old_request(subject, payload, opts) if opts[:old_style]
|
460
|
+
|
461
|
+
token = nil
|
462
|
+
inbox = nil
|
463
|
+
future = nil
|
464
|
+
response = nil
|
465
|
+
timeout = opts[:timeout] ||= 0.5
|
466
|
+
synchronize do
|
467
|
+
start_resp_mux_sub! unless @resp_sub_prefix
|
468
|
+
|
469
|
+
# Create token for this request.
|
470
|
+
token = @nuid.next
|
471
|
+
inbox = "#{@resp_sub_prefix}.#{token}"
|
472
|
+
|
473
|
+
# Create the a future for the request that will
|
474
|
+
# get signaled when it receives the request.
|
475
|
+
future = @resp_sub.new_cond
|
476
|
+
@resp_map[token][:future] = future
|
477
|
+
end
|
478
|
+
|
479
|
+
# Publish request and wait for reply.
|
480
|
+
publish(subject, payload, inbox)
|
481
|
+
begin
|
482
|
+
with_nats_timeout(timeout) do
|
483
|
+
@resp_sub.synchronize do
|
484
|
+
future.wait(timeout)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
rescue NATS::IO::Timeout => e
|
488
|
+
synchronize { @resp_map.delete(token) }
|
489
|
+
raise e
|
490
|
+
end
|
491
|
+
|
492
|
+
# Check if there is a response already.
|
493
|
+
synchronize do
|
494
|
+
result = @resp_map[token]
|
495
|
+
response = result[:response]
|
496
|
+
@resp_map.delete(token)
|
497
|
+
end
|
498
|
+
|
499
|
+
if response and response.header
|
500
|
+
status = response.header[STATUS_HDR]
|
501
|
+
raise NoRespondersError if status == "503"
|
502
|
+
end
|
503
|
+
|
504
|
+
response
|
505
|
+
end
|
506
|
+
|
507
|
+
# Makes a NATS request using a NATS::Msg that may include headers.
|
508
|
+
def request_msg(msg, opts={})
|
509
|
+
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
510
|
+
raise BadSubject if !msg.subject or msg.subject.empty?
|
511
|
+
|
512
|
+
token = nil
|
513
|
+
inbox = nil
|
514
|
+
future = nil
|
515
|
+
response = nil
|
516
|
+
timeout = opts[:timeout] ||= 0.5
|
517
|
+
synchronize do
|
518
|
+
start_resp_mux_sub! unless @resp_sub_prefix
|
519
|
+
|
520
|
+
# Create token for this request.
|
521
|
+
token = @nuid.next
|
522
|
+
inbox = "#{@resp_sub_prefix}.#{token}"
|
523
|
+
|
524
|
+
# Create the a future for the request that will
|
525
|
+
# get signaled when it receives the request.
|
526
|
+
future = @resp_sub.new_cond
|
527
|
+
@resp_map[token][:future] = future
|
528
|
+
end
|
529
|
+
msg.reply = inbox
|
530
|
+
msg.data ||= ''
|
531
|
+
msg_size = msg.data.bytesize
|
532
|
+
|
533
|
+
# Publish request and wait for reply.
|
534
|
+
publish_msg(msg)
|
535
|
+
begin
|
536
|
+
with_nats_timeout(timeout) do
|
537
|
+
@resp_sub.synchronize do
|
538
|
+
future.wait(timeout)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
rescue NATS::IO::Timeout => e
|
542
|
+
synchronize { @resp_map.delete(token) }
|
543
|
+
raise e
|
544
|
+
end
|
545
|
+
|
546
|
+
# Check if there is a response already.
|
547
|
+
synchronize do
|
548
|
+
result = @resp_map[token]
|
549
|
+
response = result[:response]
|
550
|
+
@resp_map.delete(token)
|
551
|
+
end
|
552
|
+
|
553
|
+
if response and response.header
|
554
|
+
status = response.header[STATUS_HDR]
|
555
|
+
raise NoRespondersError if status == "503"
|
556
|
+
end
|
557
|
+
|
558
|
+
response
|
559
|
+
end
|
560
|
+
|
561
|
+
# Sends a request creating an ephemeral subscription for the request,
|
562
|
+
# expecting a single response or raising a timeout in case the request
|
563
|
+
# is not retrieved within the specified deadline.
|
564
|
+
# If given a callback, then the request happens asynchronously.
|
565
|
+
def old_request(subject, payload, opts={}, &blk)
|
351
566
|
return unless subject
|
352
567
|
inbox = new_inbox
|
353
568
|
|
@@ -355,11 +570,13 @@ module NATS
|
|
355
570
|
# the messages asynchronously and return the sid.
|
356
571
|
if blk
|
357
572
|
opts[:max] ||= 1
|
358
|
-
s = subscribe(inbox, opts) do |msg, reply|
|
573
|
+
s = subscribe(inbox, opts) do |msg, reply, subject, header|
|
359
574
|
case blk.arity
|
360
575
|
when 0 then blk.call
|
361
576
|
when 1 then blk.call(msg)
|
362
|
-
|
577
|
+
when 2 then blk.call(msg, reply)
|
578
|
+
when 3 then blk.call(msg, reply, subject)
|
579
|
+
else blk.call(msg, reply, subject, header)
|
363
580
|
end
|
364
581
|
end
|
365
582
|
publish(subject, payload, inbox)
|
@@ -485,8 +702,7 @@ module NATS
|
|
485
702
|
process_op_error(e)
|
486
703
|
end
|
487
704
|
|
488
|
-
def process_msg(subject, sid, reply, data)
|
489
|
-
# Accounting
|
705
|
+
def process_msg(subject, sid, reply, data, header)
|
490
706
|
@stats[:in_msgs] += 1
|
491
707
|
@stats[:in_bytes] += data.size
|
492
708
|
|
@@ -495,7 +711,7 @@ module NATS
|
|
495
711
|
synchronize { sub = @subs[sid] }
|
496
712
|
return unless sub
|
497
713
|
|
498
|
-
|
714
|
+
err = nil
|
499
715
|
sub.synchronize do
|
500
716
|
sub.received += 1
|
501
717
|
|
@@ -516,29 +732,64 @@ module NATS
|
|
516
732
|
# do so here already while holding the lock and return
|
517
733
|
if sub.future
|
518
734
|
future = sub.future
|
519
|
-
|
735
|
+
hdr = process_hdr(header)
|
736
|
+
sub.response = Msg.new(subject: subject, reply: reply, data: data, header: hdr)
|
520
737
|
future.signal
|
521
738
|
|
522
739
|
return
|
523
|
-
elsif sub.
|
740
|
+
elsif sub.pending_queue
|
524
741
|
# Async subscribers use a sized queue for processing
|
525
742
|
# and should be able to consume messages in parallel.
|
526
743
|
if sub.pending_queue.size >= sub.pending_msgs_limit \
|
527
744
|
or sub.pending_size >= sub.pending_bytes_limit then
|
528
|
-
|
745
|
+
err = SlowConsumer.new("nats: slow consumer, messages dropped")
|
529
746
|
else
|
747
|
+
hdr = process_hdr(header)
|
748
|
+
|
530
749
|
# Only dispatch message when sure that it would not block
|
531
750
|
# the main read loop from the parser.
|
532
|
-
|
751
|
+
msg = Msg.new(subject: subject, reply: reply, data: data, header: hdr)
|
752
|
+
sub.pending_queue << msg
|
533
753
|
sub.pending_size += data.size
|
534
754
|
end
|
535
755
|
end
|
536
756
|
end
|
537
757
|
|
538
758
|
synchronize do
|
539
|
-
@last_err =
|
540
|
-
@err_cb.call(
|
541
|
-
end if
|
759
|
+
@last_err = err
|
760
|
+
@err_cb.call(err) if @err_cb
|
761
|
+
end if err
|
762
|
+
end
|
763
|
+
|
764
|
+
def process_hdr(header)
|
765
|
+
hdr = nil
|
766
|
+
if header
|
767
|
+
hdr = {}
|
768
|
+
lines = header.lines
|
769
|
+
|
770
|
+
# Check if it is an inline status and description.
|
771
|
+
if lines.count <= 2
|
772
|
+
status_hdr = lines.first.rstrip
|
773
|
+
hdr[STATUS_HDR] = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN)
|
774
|
+
|
775
|
+
if NATS_HDR_LINE_SIZE+2 < status_hdr.bytesize
|
776
|
+
desc = status_hdr.slice(NATS_HDR_LINE_SIZE+STATUS_MSG_LEN, status_hdr.bytesize)
|
777
|
+
hdr[DESC_HDR] = desc unless desc.empty?
|
778
|
+
end
|
779
|
+
end
|
780
|
+
begin
|
781
|
+
lines.slice(1, header.size).each do |line|
|
782
|
+
line.rstrip!
|
783
|
+
next if line.empty?
|
784
|
+
key, value = line.strip.split(/\s*:\s*/, 2)
|
785
|
+
hdr[key] = value
|
786
|
+
end
|
787
|
+
rescue => e
|
788
|
+
err = e
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
hdr
|
542
793
|
end
|
543
794
|
|
544
795
|
def process_info(line)
|
@@ -559,7 +810,8 @@ module NATS
|
|
559
810
|
if connect_urls
|
560
811
|
srvs = []
|
561
812
|
connect_urls.each do |url|
|
562
|
-
|
813
|
+
scheme = client_using_secure_connection? ? "tls" : "nats"
|
814
|
+
u = URI.parse("#{scheme}://#{url}")
|
563
815
|
|
564
816
|
# Skip in case it is the current server which we already know
|
565
817
|
next if @uri.host == u.host && @uri.port == u.port
|
@@ -678,6 +930,10 @@ module NATS
|
|
678
930
|
@uri.scheme == "tls" || @tls
|
679
931
|
end
|
680
932
|
|
933
|
+
def single_url_connect_used?
|
934
|
+
@single_url_connect_used
|
935
|
+
end
|
936
|
+
|
681
937
|
def send_command(command)
|
682
938
|
@pending_size += command.bytesize
|
683
939
|
@pending_queue << command
|
@@ -699,13 +955,31 @@ module NATS
|
|
699
955
|
}
|
700
956
|
cs[:name] = @options[:name] if @options[:name]
|
701
957
|
|
702
|
-
|
958
|
+
case
|
959
|
+
when auth_connection?
|
703
960
|
if @uri.password
|
704
961
|
cs[:user] = @uri.user
|
705
962
|
cs[:pass] = @uri.password
|
706
963
|
else
|
707
964
|
cs[:auth_token] = @uri.user
|
708
965
|
end
|
966
|
+
when @user_credentials
|
967
|
+
nonce = @server_info[:nonce]
|
968
|
+
cs[:jwt] = @user_jwt_cb.call
|
969
|
+
cs[:sig] = @signature_cb.call(nonce)
|
970
|
+
when @nkeys_seed
|
971
|
+
nonce = @server_info[:nonce]
|
972
|
+
cs[:nkey] = @user_nkey_cb.call
|
973
|
+
cs[:sig] = @signature_cb.call(nonce)
|
974
|
+
end
|
975
|
+
|
976
|
+
if @server_info[:headers]
|
977
|
+
cs[:headers] = @server_info[:headers]
|
978
|
+
cs[:no_responders] = if @options[:no_responders] == false
|
979
|
+
@options[:no_responders]
|
980
|
+
else
|
981
|
+
@server_info[:headers]
|
982
|
+
end
|
709
983
|
end
|
710
984
|
|
711
985
|
"CONNECT #{cs.to_json}#{CR_LF}"
|
@@ -858,8 +1132,13 @@ module NATS
|
|
858
1132
|
if !line or line.empty?
|
859
1133
|
raise ConnectError.new("nats: protocol exception, INFO not received")
|
860
1134
|
end
|
861
|
-
|
862
|
-
|
1135
|
+
|
1136
|
+
if match = line.match(NATS::Protocol::INFO)
|
1137
|
+
info_json = match.captures.first
|
1138
|
+
process_info(info_json)
|
1139
|
+
else
|
1140
|
+
raise ConnectError.new("nats: protocol exception, INFO not valid")
|
1141
|
+
end
|
863
1142
|
|
864
1143
|
case
|
865
1144
|
when (server_using_secure_connection? and client_using_secure_connection?)
|
@@ -946,7 +1225,12 @@ module NATS
|
|
946
1225
|
@stats[:reconnects] += 1
|
947
1226
|
|
948
1227
|
# Set hostname to use for TLS hostname verification
|
949
|
-
|
1228
|
+
if client_using_secure_connection? and single_url_connect_used?
|
1229
|
+
# Reuse original hostname name in case of using TLS.
|
1230
|
+
@hostname ||= srv[:hostname]
|
1231
|
+
else
|
1232
|
+
@hostname = srv[:hostname]
|
1233
|
+
end
|
950
1234
|
|
951
1235
|
# Established TCP connection successfully so can start connect
|
952
1236
|
process_connect_init
|
@@ -1093,6 +1377,47 @@ module NATS
|
|
1093
1377
|
@ping_interval_thread.abort_on_exception = true
|
1094
1378
|
end
|
1095
1379
|
|
1380
|
+
# Prepares requests subscription that handles the responses
|
1381
|
+
# for the new style request response.
|
1382
|
+
def start_resp_mux_sub!
|
1383
|
+
@resp_sub_prefix = "_INBOX.#{@nuid.next}"
|
1384
|
+
@resp_map = Hash.new { |h,k| h[k] = { }}
|
1385
|
+
|
1386
|
+
@resp_sub = Subscription.new
|
1387
|
+
@resp_sub.subject = "#{@resp_sub_prefix}.*"
|
1388
|
+
@resp_sub.received = 0
|
1389
|
+
|
1390
|
+
# FIXME: Allow setting pending limits for responses mux subscription.
|
1391
|
+
@resp_sub.pending_msgs_limit = DEFAULT_SUB_PENDING_MSGS_LIMIT
|
1392
|
+
@resp_sub.pending_bytes_limit = DEFAULT_SUB_PENDING_BYTES_LIMIT
|
1393
|
+
@resp_sub.pending_queue = SizedQueue.new(@resp_sub.pending_msgs_limit)
|
1394
|
+
@resp_sub.wait_for_msgs_t = Thread.new do
|
1395
|
+
loop do
|
1396
|
+
msg = @resp_sub.pending_queue.pop
|
1397
|
+
@resp_sub.pending_size -= msg.data.size
|
1398
|
+
|
1399
|
+
# Pick the token and signal the request under the mutex
|
1400
|
+
# from the subscription itself.
|
1401
|
+
token = msg.subject.split('.').last
|
1402
|
+
future = nil
|
1403
|
+
synchronize do
|
1404
|
+
future = @resp_map[token][:future]
|
1405
|
+
@resp_map[token][:response] = msg
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
# Signal back that the response has arrived.
|
1409
|
+
@resp_sub.synchronize do
|
1410
|
+
future.signal
|
1411
|
+
end
|
1412
|
+
end
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
sid = (@ssid += 1)
|
1416
|
+
@subs[sid] = @resp_sub
|
1417
|
+
send_command("SUB #{@resp_sub.subject} #{sid}#{CR_LF}")
|
1418
|
+
@flush_queue << :sub
|
1419
|
+
end
|
1420
|
+
|
1096
1421
|
def can_reuse_server?(server)
|
1097
1422
|
return false if server.nil?
|
1098
1423
|
|
@@ -1125,6 +1450,115 @@ module NATS
|
|
1125
1450
|
connect_timeout: DEFAULT_CONNECT_TIMEOUT
|
1126
1451
|
})
|
1127
1452
|
end
|
1453
|
+
|
1454
|
+
def setup_nkeys_connect
|
1455
|
+
begin
|
1456
|
+
require 'nkeys'
|
1457
|
+
require 'base64'
|
1458
|
+
rescue LoadError
|
1459
|
+
raise(Error, "nkeys is not installed")
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
case
|
1463
|
+
when @nkeys_seed
|
1464
|
+
@user_nkey_cb = proc {
|
1465
|
+
seed = File.read(@nkeys_seed).chomp
|
1466
|
+
kp = NKEYS::from_seed(seed)
|
1467
|
+
|
1468
|
+
# Take a copy since original will be gone with the wipe.
|
1469
|
+
pub_key = kp.public_key.dup
|
1470
|
+
kp.wipe!
|
1471
|
+
|
1472
|
+
pub_key
|
1473
|
+
}
|
1474
|
+
|
1475
|
+
@signature_cb = proc { |nonce|
|
1476
|
+
seed = File.read(@nkeys_seed).chomp
|
1477
|
+
kp = NKEYS::from_seed(seed)
|
1478
|
+
raw_signed = kp.sign(nonce)
|
1479
|
+
kp.wipe!
|
1480
|
+
encoded = Base64.urlsafe_encode64(raw_signed)
|
1481
|
+
encoded.gsub('=', '')
|
1482
|
+
}
|
1483
|
+
when @user_credentials
|
1484
|
+
# When the credentials are within a single decorated file.
|
1485
|
+
@user_jwt_cb = proc {
|
1486
|
+
jwt_start = "BEGIN NATS USER JWT".freeze
|
1487
|
+
found = false
|
1488
|
+
jwt = nil
|
1489
|
+
File.readlines(@user_credentials).each do |line|
|
1490
|
+
case
|
1491
|
+
when found
|
1492
|
+
jwt = line.chomp
|
1493
|
+
break
|
1494
|
+
when line.include?(jwt_start)
|
1495
|
+
found = true
|
1496
|
+
end
|
1497
|
+
end
|
1498
|
+
raise(Error, "No JWT found in #{@user_credentials}") if not found
|
1499
|
+
|
1500
|
+
jwt
|
1501
|
+
}
|
1502
|
+
|
1503
|
+
@signature_cb = proc { |nonce|
|
1504
|
+
seed_start = "BEGIN USER NKEY SEED".freeze
|
1505
|
+
found = false
|
1506
|
+
seed = nil
|
1507
|
+
File.readlines(@user_credentials).each do |line|
|
1508
|
+
case
|
1509
|
+
when found
|
1510
|
+
seed = line.chomp
|
1511
|
+
break
|
1512
|
+
when line.include?(seed_start)
|
1513
|
+
found = true
|
1514
|
+
end
|
1515
|
+
end
|
1516
|
+
raise(Error, "No nkey user seed found in #{@user_credentials}") if not found
|
1517
|
+
|
1518
|
+
kp = NKEYS::from_seed(seed)
|
1519
|
+
raw_signed = kp.sign(nonce)
|
1520
|
+
|
1521
|
+
# seed is a reference so also cleared when doing wipe,
|
1522
|
+
# which can be done since Ruby strings are mutable.
|
1523
|
+
kp.wipe
|
1524
|
+
encoded = Base64.urlsafe_encode64(raw_signed)
|
1525
|
+
|
1526
|
+
# Remove padding
|
1527
|
+
encoded.gsub('=', '')
|
1528
|
+
}
|
1529
|
+
end
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
def process_uri(uris)
|
1533
|
+
connect_uris = []
|
1534
|
+
uris.split(',').each do |uri|
|
1535
|
+
opts = {}
|
1536
|
+
|
1537
|
+
# Scheme
|
1538
|
+
if uri.include?("://")
|
1539
|
+
scheme, uri = uri.split("://")
|
1540
|
+
opts[:scheme] = scheme
|
1541
|
+
else
|
1542
|
+
opts[:scheme] = 'nats'
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
# UserInfo
|
1546
|
+
if uri.include?("@")
|
1547
|
+
userinfo, endpoint = uri.split("@")
|
1548
|
+
host, port = endpoint.split(":")
|
1549
|
+
opts[:userinfo] = userinfo
|
1550
|
+
else
|
1551
|
+
host, port = uri.split(":")
|
1552
|
+
end
|
1553
|
+
|
1554
|
+
# Host and Port
|
1555
|
+
opts[:host] = host || "localhost"
|
1556
|
+
opts[:port] = port || DEFAULT_PORT
|
1557
|
+
|
1558
|
+
connect_uris << URI::Generic.build(opts)
|
1559
|
+
end
|
1560
|
+
connect_uris
|
1561
|
+
end
|
1128
1562
|
end
|
1129
1563
|
|
1130
1564
|
# Implementation adapted from https://github.com/redis/redis-rb
|
@@ -1254,7 +1688,7 @@ module NATS
|
|
1254
1688
|
end
|
1255
1689
|
end
|
1256
1690
|
|
1257
|
-
Msg = Struct.new(:subject, :reply, :data)
|
1691
|
+
Msg = Struct.new(:subject, :reply, :data, :header, keyword_init: true)
|
1258
1692
|
|
1259
1693
|
class Subscription
|
1260
1694
|
include MonitorMixin
|
data/lib/nats/io/parser.rb
CHANGED
@@ -1,7 +1,22 @@
|
|
1
|
+
# Copyright 2016-2018 The NATS Authors
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
|
+
# you may not use this file except in compliance with the License.
|
4
|
+
# You may obtain a copy of the License at
|
5
|
+
#
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11
|
+
# See the License for the specific language governing permissions and
|
12
|
+
# limitations under the License.
|
13
|
+
#
|
14
|
+
|
1
15
|
module NATS
|
2
16
|
module Protocol
|
3
17
|
|
4
18
|
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
|
19
|
+
HMSG = /\AHMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([\d]+)\s+(\d+)\r\n/i
|
5
20
|
OK = /\A\+OK\s*\r\n/i
|
6
21
|
ERR = /\A-ERR\s+('.+')?\r\n/i
|
7
22
|
PING = /\APING\s*\r\n/i
|
@@ -35,6 +50,7 @@ module NATS
|
|
35
50
|
@sid = nil
|
36
51
|
@reply = nil
|
37
52
|
@needed = nil
|
53
|
+
@header_needed = nil
|
38
54
|
end
|
39
55
|
|
40
56
|
def parse(data)
|
@@ -47,6 +63,10 @@ module NATS
|
|
47
63
|
@buf = $'
|
48
64
|
@sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
|
49
65
|
@parse_state = AWAITING_MSG_PAYLOAD
|
66
|
+
when HMSG
|
67
|
+
@buf = $'
|
68
|
+
@sub, @sid, @reply, @header_needed, @needed = $1, $2.to_i, $4, $5.to_i, $6.to_i
|
69
|
+
@parse_state = AWAITING_MSG_PAYLOAD
|
50
70
|
when OK # No-op right now
|
51
71
|
@buf = $'
|
52
72
|
when ERR
|
@@ -75,9 +95,17 @@ module NATS
|
|
75
95
|
|
76
96
|
when AWAITING_MSG_PAYLOAD
|
77
97
|
return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
|
78
|
-
|
79
|
-
|
80
|
-
|
98
|
+
if @header_needed
|
99
|
+
hbuf = @buf.slice(0, @header_needed)
|
100
|
+
payload = @buf.slice(@header_needed, (@needed-@header_needed))
|
101
|
+
@nc.process_msg(@sub, @sid, @reply, payload, hbuf)
|
102
|
+
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
103
|
+
else
|
104
|
+
@nc.process_msg(@sub, @sid, @reply, @buf.slice(0, @needed), nil)
|
105
|
+
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
106
|
+
end
|
107
|
+
|
108
|
+
@sub = @sid = @reply = @needed = @header_needed = nil
|
81
109
|
@parse_state = AWAITING_CONTROL_LINE
|
82
110
|
@buf = nil if (@buf && @buf.empty?)
|
83
111
|
end
|
data/lib/nats/io/version.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
1
|
+
# Copyright 2016-2018 The NATS Authors
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
|
+
# you may not use this file except in compliance with the License.
|
4
|
+
# You may obtain a copy of the License at
|
5
|
+
#
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11
|
+
# See the License for the specific language governing permissions and
|
12
|
+
# limitations under the License.
|
13
|
+
#
|
14
|
+
|
1
15
|
module NATS
|
2
16
|
module IO
|
3
17
|
# NOTE: These are all announced to the server on CONNECT
|
4
|
-
VERSION = "0.
|
5
|
-
LANG = "#{RUBY_ENGINE}
|
18
|
+
VERSION = "0.7.0"
|
19
|
+
LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
|
6
20
|
PROTOCOL = 1
|
7
21
|
end
|
8
22
|
end
|
data/lib/nats/nuid.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Copyright 2016-2018 The NATS Authors
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
|
+
# you may not use this file except in compliance with the License.
|
4
|
+
# You may obtain a copy of the License at
|
5
|
+
#
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
7
|
+
#
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11
|
+
# See the License for the specific language governing permissions and
|
12
|
+
# limitations under the License.
|
13
|
+
#
|
14
|
+
require 'securerandom'
|
15
|
+
|
16
|
+
module NATS
|
17
|
+
class NUID
|
18
|
+
DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
|
19
|
+
BASE = 62
|
20
|
+
PREFIX_LENGTH = 12
|
21
|
+
SEQ_LENGTH = 10
|
22
|
+
TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH
|
23
|
+
MAX_SEQ = BASE**10
|
24
|
+
MIN_INC = 33
|
25
|
+
MAX_INC = 333
|
26
|
+
INC = MAX_INC - MIN_INC
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@prand = Random.new
|
30
|
+
@seq = @prand.rand(MAX_SEQ)
|
31
|
+
@inc = MIN_INC + @prand.rand(INC)
|
32
|
+
@prefix = ''
|
33
|
+
randomize_prefix!
|
34
|
+
end
|
35
|
+
|
36
|
+
def next
|
37
|
+
@seq += @inc
|
38
|
+
if @seq >= MAX_SEQ
|
39
|
+
randomize_prefix!
|
40
|
+
reset_sequential!
|
41
|
+
end
|
42
|
+
l = @seq
|
43
|
+
|
44
|
+
# Do this inline 10 times to avoid even more extra allocs,
|
45
|
+
# then use string interpolation of everything which works
|
46
|
+
# faster for doing concat.
|
47
|
+
s_10 = DIGITS[l % BASE];
|
48
|
+
|
49
|
+
# Ugly, but parallel assignment is slightly faster here...
|
50
|
+
s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = \
|
51
|
+
(l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]),\
|
52
|
+
(l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]),\
|
53
|
+
(l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE])
|
54
|
+
"#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def randomize_prefix!
|
58
|
+
@prefix = \
|
59
|
+
SecureRandom.random_bytes(PREFIX_LENGTH).each_byte
|
60
|
+
.reduce('') do |prefix, n|
|
61
|
+
prefix << DIGITS[n % BASE]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def reset_sequential!
|
68
|
+
@seq = @prand.rand(MAX_SEQ)
|
69
|
+
@inc = MIN_INC + @prand.rand(INC)
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
@@nuid = NUID.new.extend(MonitorMixin)
|
74
|
+
def next
|
75
|
+
@@nuid.synchronize do
|
76
|
+
@@nuid.next
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nats-pure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Waldemar Quevedo
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: NATS is an open-source, high-performance, lightweight cloud messaging
|
14
14
|
system.
|
15
15
|
email:
|
16
|
-
- wally@
|
16
|
+
- wally@synadia.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
@@ -21,11 +21,12 @@ files:
|
|
21
21
|
- lib/nats/io/client.rb
|
22
22
|
- lib/nats/io/parser.rb
|
23
23
|
- lib/nats/io/version.rb
|
24
|
+
- lib/nats/nuid.rb
|
24
25
|
homepage: https://nats.io
|
25
26
|
licenses:
|
26
|
-
-
|
27
|
+
- Apache-2.0
|
27
28
|
metadata: {}
|
28
|
-
post_install_message:
|
29
|
+
post_install_message:
|
29
30
|
rdoc_options: []
|
30
31
|
require_paths:
|
31
32
|
- lib
|
@@ -40,9 +41,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
41
|
- !ruby/object:Gem::Version
|
41
42
|
version: '0'
|
42
43
|
requirements: []
|
43
|
-
|
44
|
-
|
45
|
-
signing_key:
|
44
|
+
rubygems_version: 3.2.22
|
45
|
+
signing_key:
|
46
46
|
specification_version: 4
|
47
47
|
summary: NATS is an open-source, high-performance, lightweight cloud messaging system.
|
48
48
|
test_files: []
|