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 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: []