nats-pure 0.6.2 → 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: 705212803ac5f40ecb4b1ffa6bcb6fa16d0153ef90c2d46ef2f937c66d340503
4
- data.tar.gz: 580ac629b82778d88de22293bb5e1f501f8090c8cbd2635728cac38f819e6752
3
+ metadata.gz: d21f9a3f83d1aaa172f0835a2d74b5109a8e937429550981427b84325f97f1d3
4
+ data.tar.gz: 48ea0ef20a7c2611023573f883384d15ebf6bf9c342f37336b25157d79963098
5
5
  SHA512:
6
- metadata.gz: e41fc0cc6a94ba6e50d7eca22810c20b9cfe133a8cb89df4734b7bbdc4053a4d2dada84cf26ea71c54c4b578af57a24bb8739437489018dfa00637017a44b5aa
7
- data.tar.gz: 6e31b6352d5771f6f6da6541a9c045081919630357efe2535f3ac29687a7c2edca7ec26385bb2dbfd75ad0279e41fd76a857aa52e118b3f7b65c3f34cc587b34
6
+ metadata.gz: b72826e0d687cee2e303f3fae2abbc34ad6fe10ce35967d2201ecd3838c27f186e70c99c880288bfd2fbdc840edebfa80f4f31e282caafb15f899b41301a5a94
7
+ data.tar.gz: b5328d07ffa92de70d32cb37b28bf1c08db2866b58902dee2f761b6c0cf758c18037262fca4f42a3ab5bf6c4f0663abb733d8322cdfe8a2e30d8bd9de9a4139f
@@ -1,4 +1,4 @@
1
- # Copyright 2016-2018 The NATS Authors
1
+ # Copyright 2016-2021 The NATS Authors
2
2
  # Licensed under the Apache License, Version 2.0 (the "License");
3
3
  # you may not use this file except in compliance with the License.
4
4
  # You may obtain a copy of the License at
@@ -64,6 +64,12 @@ module NATS
64
64
  PING_REQUEST = ("PING#{CR_LF}".freeze)
65
65
  PONG_RESPONSE = ("PONG#{CR_LF}".freeze)
66
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
+
67
73
  SUB_OP = ('SUB'.freeze)
68
74
  EMPTY_MSG = (''.freeze)
69
75
 
@@ -91,6 +97,9 @@ module NATS
91
97
  # When we cannot connect since there are no servers available.
92
98
  class NoServersError < ConnectError; end
93
99
 
100
+ # When there are no subscribers available to respond.
101
+ class NoRespondersError < ConnectError; end
102
+
94
103
  # When the connection exhausts max number of pending pings replies.
95
104
  class StaleConnectionError < Error; end
96
105
 
@@ -346,6 +355,36 @@ module NATS
346
355
  @flush_queue << :pub if @flush_queue.empty?
347
356
  end
348
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
+
349
388
  # Create subscription which is dispatched asynchronously
350
389
  # messages to a callback.
351
390
  def subscribe(subject, opts={}, &callback)
@@ -393,7 +432,8 @@ module NATS
393
432
  when 0 then cb.call
394
433
  when 1 then cb.call(msg.data)
395
434
  when 2 then cb.call(msg.data, msg.reply)
396
- 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)
397
437
  end
398
438
  rescue => e
399
439
  synchronize do
@@ -411,11 +451,12 @@ module NATS
411
451
  # It times out in case the request is not retrieved within the
412
452
  # specified deadline.
413
453
  # If given a callback, then the request happens asynchronously.
414
- def request(subject, payload, opts={}, &blk)
415
- return unless subject
454
+ def request(subject, payload="", opts={}, &blk)
455
+ raise BadSubject if !subject or subject.empty?
416
456
 
417
457
  # If a block was given then fallback to method using auto unsubscribe.
418
458
  return old_request(subject, payload, opts, &blk) if blk
459
+ return old_request(subject, payload, opts) if opts[:old_style]
419
460
 
420
461
  token = nil
421
462
  inbox = nil
@@ -437,16 +478,81 @@ module NATS
437
478
 
438
479
  # Publish request and wait for reply.
439
480
  publish(subject, payload, inbox)
440
- with_nats_timeout(timeout) do
441
- @resp_sub.synchronize do
442
- future.wait(timeout)
481
+ begin
482
+ with_nats_timeout(timeout) do
483
+ @resp_sub.synchronize do
484
+ future.wait(timeout)
485
+ end
443
486
  end
487
+ rescue NATS::IO::Timeout => e
488
+ synchronize { @resp_map.delete(token) }
489
+ raise e
444
490
  end
445
491
 
446
- # Check if there is a response already
492
+ # Check if there is a response already.
447
493
  synchronize do
448
494
  result = @resp_map[token]
449
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"
450
556
  end
451
557
 
452
558
  response
@@ -464,11 +570,13 @@ module NATS
464
570
  # the messages asynchronously and return the sid.
465
571
  if blk
466
572
  opts[:max] ||= 1
467
- s = subscribe(inbox, opts) do |msg, reply|
573
+ s = subscribe(inbox, opts) do |msg, reply, subject, header|
468
574
  case blk.arity
469
575
  when 0 then blk.call
470
576
  when 1 then blk.call(msg)
471
- 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)
472
580
  end
473
581
  end
474
582
  publish(subject, payload, inbox)
@@ -594,8 +702,7 @@ module NATS
594
702
  process_op_error(e)
595
703
  end
596
704
 
597
- def process_msg(subject, sid, reply, data)
598
- # Accounting
705
+ def process_msg(subject, sid, reply, data, header)
599
706
  @stats[:in_msgs] += 1
600
707
  @stats[:in_bytes] += data.size
601
708
 
@@ -604,7 +711,7 @@ module NATS
604
711
  synchronize { sub = @subs[sid] }
605
712
  return unless sub
606
713
 
607
- sc = nil
714
+ err = nil
608
715
  sub.synchronize do
609
716
  sub.received += 1
610
717
 
@@ -625,7 +732,8 @@ module NATS
625
732
  # do so here already while holding the lock and return
626
733
  if sub.future
627
734
  future = sub.future
628
- 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)
629
737
  future.signal
630
738
 
631
739
  return
@@ -634,20 +742,54 @@ module NATS
634
742
  # and should be able to consume messages in parallel.
635
743
  if sub.pending_queue.size >= sub.pending_msgs_limit \
636
744
  or sub.pending_size >= sub.pending_bytes_limit then
637
- sc = SlowConsumer.new("nats: slow consumer, messages dropped")
745
+ err = SlowConsumer.new("nats: slow consumer, messages dropped")
638
746
  else
747
+ hdr = process_hdr(header)
748
+
639
749
  # Only dispatch message when sure that it would not block
640
750
  # the main read loop from the parser.
641
- 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
642
753
  sub.pending_size += data.size
643
754
  end
644
755
  end
645
756
  end
646
757
 
647
758
  synchronize do
648
- @last_err = sc
649
- @err_cb.call(sc) if @err_cb
650
- 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
651
793
  end
652
794
 
653
795
  def process_info(line)
@@ -831,6 +973,15 @@ module NATS
831
973
  cs[:sig] = @signature_cb.call(nonce)
832
974
  end
833
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
983
+ end
984
+
834
985
  "CONNECT #{cs.to_json}#{CR_LF}"
835
986
  end
836
987
 
@@ -1537,7 +1688,7 @@ module NATS
1537
1688
  end
1538
1689
  end
1539
1690
 
1540
- Msg = Struct.new(:subject, :reply, :data)
1691
+ Msg = Struct.new(:subject, :reply, :data, :header, keyword_init: true)
1541
1692
 
1542
1693
  class Subscription
1543
1694
  include MonitorMixin
@@ -16,6 +16,7 @@ module NATS
16
16
  module Protocol
17
17
 
18
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
19
20
  OK = /\A\+OK\s*\r\n/i
20
21
  ERR = /\A-ERR\s+('.+')?\r\n/i
21
22
  PING = /\APING\s*\r\n/i
@@ -49,6 +50,7 @@ module NATS
49
50
  @sid = nil
50
51
  @reply = nil
51
52
  @needed = nil
53
+ @header_needed = nil
52
54
  end
53
55
 
54
56
  def parse(data)
@@ -61,6 +63,10 @@ module NATS
61
63
  @buf = $'
62
64
  @sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
63
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
64
70
  when OK # No-op right now
65
71
  @buf = $'
66
72
  when ERR
@@ -89,9 +95,17 @@ module NATS
89
95
 
90
96
  when AWAITING_MSG_PAYLOAD
91
97
  return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
92
- @nc.process_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
93
- @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
94
- @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
95
109
  @parse_state = AWAITING_CONTROL_LINE
96
110
  @buf = nil if (@buf && @buf.empty?)
97
111
  end
@@ -15,8 +15,8 @@
15
15
  module NATS
16
16
  module IO
17
17
  # NOTE: These are all announced to the server on CONNECT
18
- VERSION = "0.6.2"
19
- LANG = "#{RUBY_ENGINE}2".freeze
18
+ VERSION = "0.7.0"
19
+ LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
20
20
  PROTOCOL = 1
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nats-pure
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
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: 2019-07-22 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.
@@ -26,7 +26,7 @@ homepage: https://nats.io
26
26
  licenses:
27
27
  - Apache-2.0
28
28
  metadata: {}
29
- post_install_message:
29
+ post_install_message:
30
30
  rdoc_options: []
31
31
  require_paths:
32
32
  - lib
@@ -41,8 +41,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0'
43
43
  requirements: []
44
- rubygems_version: 3.0.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: []