nats-pure 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59129f0c5e0180f95490a2ca5e2acb8cf0c1d630c32e4d4ab6f0cf859242500b
4
- data.tar.gz: c23db05317523a59762b9b2e73917165baf8ee3a898aaa74e84a74b0977c1942
3
+ metadata.gz: d21f9a3f83d1aaa172f0835a2d74b5109a8e937429550981427b84325f97f1d3
4
+ data.tar.gz: 48ea0ef20a7c2611023573f883384d15ebf6bf9c342f37336b25157d79963098
5
5
  SHA512:
6
- metadata.gz: b82fdc592bea6ddc5ca1e0792b76071dd09db70a86fcaef517207d33c339d1b105076efd2b3d3cc83960a6b470739d01ec9d2f41574f57111935e0245eb02e5d
7
- data.tar.gz: 4439c2f2f0c3c6118b5a4b93587a60ecb069f8b85e0907539bec6a497a8394a1f78a9a961aa2565436e7c7ab4ee3bc1bd459310935499da67c1d7e7c11a0d5c7
6
+ metadata.gz: b72826e0d687cee2e303f3fae2abbc34ad6fe10ce35967d2201ecd3838c27f186e70c99c880288bfd2fbdc840edebfa80f4f31e282caafb15f899b41301a5a94
7
+ data.tar.gz: b5328d07ffa92de70d32cb37b28bf1c08db2866b58902dee2f761b6c0cf758c18037262fca4f42a3ab5bf6c4f0663abb733d8322cdfe8a2e30d8bd9de9a4139f
@@ -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
- @hostname = srv[:hostname]
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
- else cb.call(msg.data, msg.reply, msg.subject)
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 or raises a timeout
348
- # in case the request is not retrieved within the specified deadline.
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
- else blk.call(msg, reply)
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
- sc = nil
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
- sub.response = Msg.new(subject, reply, data)
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.callback
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
- sc = SlowConsumer.new("nats: slow consumer, messages dropped")
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
- sub.pending_queue << Msg.new(subject, reply, data)
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 = sc
540
- @err_cb.call(sc) if @err_cb
541
- end if sc
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
- u = URI.parse("nats://#{url}")
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
- if auth_connection?
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
- _, info_json = line.split(' ')
862
- process_info(info_json)
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
- @hostname = srv[:hostname]
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
@@ -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
- @nc.process_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
79
- @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
80
- @sub = @sid = @reply = @needed = nil
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
@@ -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.4.0"
5
- LANG = "#{RUBY_ENGINE}2".freeze
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.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: 2018-03-15 00:00:00.000000000 Z
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@apcera.com
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
- - MIT
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
- rubyforge_project:
44
- rubygems_version: 2.7.3
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: []