nats 0.9.2 → 0.10.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: 55e7d5acd3bc948f6238b7b294e5c7dfd03698e0173b677115bb2361a29db842
4
- data.tar.gz: a1d0c1d206407117fbe35604a9e2b4f0f5954fcda03af9dd23be2ed0dacdeb69
3
+ metadata.gz: 8caff8d7b6a0c87a312c9942174fe3c455e2cfc1c3abdb7c6baf7eb5db7445ef
4
+ data.tar.gz: 2dd5eb84f34dea3c6509c732b8da35d095af409fb955de312bc75c0b5306c7b0
5
5
  SHA512:
6
- metadata.gz: 5d11348d74d766bcf25169b3af693f4cd1bc5a2b1adb4d60919feb44718670c162cd93a0bf728e4f8624eb2c54ae952ab556f725a3e7254be91923df4b2b1986
7
- data.tar.gz: 29b9b7a9bd9152fa19491a9ac56670564549656102422b383887d919ad32268ce481f7cf2a74be07df232fa25d802fa1791f850503f82a40eab49b4e8c5dfdd7
6
+ metadata.gz: 2d8e2aa2738f152cc9f15bb1a5759714062535d55a6c449fc0a55e61b2cd59f45d37e39129fade9acf9cb0a82c2f5bf555f00fd82302e97ec9ec603ee2d2e493
7
+ data.tar.gz: '07828fd12c5c5a89eebfabaf8dae0315fa0bb0eb5dd013fee13282a63a5c037e04d2a072424fd97c266cabeb9d465ad3f79b66c4ace6c1280af85a4acf5da203'
data/README.md CHANGED
@@ -3,14 +3,14 @@
3
3
  A [Ruby](http://ruby-lang.org) client for the [NATS messaging system](https://nats.io).
4
4
 
5
5
  [![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
6
- [![Build Status](https://travis-ci.org/nats-io/ruby-nats.svg)](http://travis-ci.org/nats-io/ruby-nats) [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&type=5&v=0.9.2)](https://rubygems.org/gems/nats/versions/0.9.2) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/nats)
6
+ [![Build Status](https://travis-ci.org/nats-io/ruby-nats.svg)](http://travis-ci.org/nats-io/ruby-nats) [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&type=5&v=0.10.0)](https://rubygems.org/gems/nats/versions/0.10.0) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/nats)
7
7
 
8
8
  ## Supported Platforms
9
9
 
10
10
  This gem and the client are known to work on the following Ruby platforms:
11
11
 
12
- - MRI 2.2, 2.3.0, 2.4.0, 2.5.0
13
- - JRuby 9.1.2.0, 9.1.15.0
12
+ - MRI 2.3.0, 2.4.0, 2.5.0
13
+ - JRuby 9.1.2.0, 9.1.15.0, 9.2.0.0
14
14
 
15
15
  If you're looking for a non-EventMachine alternative, check out the [nats-pure](https://github.com/nats-io/pure-ruby-nats) gem.
16
16
 
@@ -108,11 +108,10 @@ end
108
108
 
109
109
  ### Auto discovery
110
110
 
111
- Starting from release `0.8.0` of the gem, the client also auto
112
- discovers new nodes announced by the server as they attach to the
113
- cluster. Reconnection logic parameters such as time to back-off on
114
- failure and max attempts apply the same to both discovered nodes and
115
- those defined explicitly on connect:
111
+ The client also auto discovers new nodes announced by the server as
112
+ they attach to the cluster. Reconnection logic parameters such as
113
+ time to back-off on failure and max attempts apply the same to both
114
+ discovered nodes and those defined explicitly on connect:
116
115
 
117
116
  ```ruby
118
117
  opts = {
@@ -149,7 +148,10 @@ NATS.unsubscribe(sid, MAX_WANTED)
149
148
  # Multiple connections
150
149
  NATS.subscribe('test') do |msg|
151
150
  puts "received msg"
152
- NATS.stop
151
+
152
+ # Gracefully disconnect from NATS after handling
153
+ # messages that have already been delivered by server.
154
+ NATS.drain
153
155
  end
154
156
 
155
157
  # Form second connection to send message on
@@ -42,6 +42,9 @@ module NATS
42
42
  DEFAULT_PING_INTERVAL = 120
43
43
  DEFAULT_PING_MAX = 2
44
44
 
45
+ # Drain mode support
46
+ DEFAULT_DRAIN_TIMEOUT = 30
47
+
45
48
  # Protocol
46
49
  # @private
47
50
  MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i #:nodoc:
@@ -93,6 +96,7 @@ module NATS
93
96
  # Create and return a connection to the server with the given options.
94
97
  # The optional block will be called when the connection has been completed.
95
98
  #
99
+ # @param [String] uri The URI or comma separated list of URIs of NATS servers to connect to.
96
100
  # @param [Hash] opts
97
101
  # @option opts [String|URI] :uri The URI to connect to, example nats://localhost:4222
98
102
  # @option opts [Boolean] :reconnect Boolean that can be used to suppress reconnect functionality.
@@ -107,7 +111,50 @@ module NATS
107
111
  # @option opts [Integer] :max_outstanding_pings Integer that can be used to set the max number of outstanding pings before declaring a connection closed.
108
112
  # @param [Block] &blk called when the connection is completed. Connection will be passed to the block.
109
113
  # @return [NATS] connection to the server.
110
- def connect(opts={}, &blk)
114
+ #
115
+ # @example Connect to local NATS server.
116
+ # NATS.connect do |nc|
117
+ # # ...
118
+ # end
119
+ #
120
+ # @example Setting custom server URI to connect.
121
+ # NATS.connect("nats://localhost:4222") do |nc|
122
+ # # ...
123
+ # end
124
+ #
125
+ # @example Setting username and password to authenticate.
126
+ # NATS.connect("nats://user:password@localhost:4222") do |nc|
127
+ # # ...
128
+ # end
129
+ #
130
+ # @example Specifying explicit list of servers via options.
131
+ # NATS.connect(servers: ["nats://127.0.0.1:4222","nats://127.0.0.1:4223","nats://127.0.0.1:4224"]) do |nc|
132
+ # # ...
133
+ # end
134
+ #
135
+ # @example Using comma separated array to define list of servers.
136
+ # NATS.connect("nats://localhost:4223,nats://localhost:4224") do |nc|
137
+ # # ...
138
+ # end
139
+ #
140
+ # @example Only specifying endpoint uses NATS default scheme and port.
141
+ # NATS.connect("demo.nats.io") do |nc|
142
+ # # ...
143
+ # end
144
+ #
145
+ # @example Setting infinite reconnect retries with 2 seconds back off against custom URI.
146
+ # NATS.connect("demo.nats.io:4222", max_reconnect_attempts: -1, reconnect_time_wait: 2) do |nc|
147
+ # # ...
148
+ # end
149
+ #
150
+ def connect(uri=nil, opts={}, &blk)
151
+ case uri
152
+ when String
153
+ opts[:uri] = process_uri(uri)
154
+ when Hash
155
+ opts = uri
156
+ end
157
+
111
158
  # Defaults
112
159
  opts[:verbose] = false if opts[:verbose].nil?
113
160
  opts[:pedantic] = false if opts[:pedantic].nil?
@@ -117,6 +164,7 @@ module NATS
117
164
  opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
118
165
  opts[:ping_interval] = DEFAULT_PING_INTERVAL if opts[:ping_interval].nil?
119
166
  opts[:max_outstanding_pings] = DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
167
+ opts[:drain_timeout] = DEFAULT_DRAIN_TIMEOUT if opts[:drain_timeout].nil?
120
168
 
121
169
  # Override with ENV
122
170
  opts[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
@@ -129,10 +177,10 @@ module NATS
129
177
  opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
130
178
  opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
131
179
  opts[:name] ||= ENV['NATS_CONNECTION_NAME']
132
-
133
-
180
+ opts[:no_echo] ||= ENV['NATS_NO_ECHO'] || false
134
181
  opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil?
135
182
  opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil?
183
+ opts[:drain_timeout] ||= ENV['NATS_DRAIN_TIMEOUT'].to_i unless ENV['NATS_DRAIN_TIMEOUT'].nil?
136
184
 
137
185
  uri = opts[:uris] || opts[:servers] || opts[:uri]
138
186
 
@@ -211,6 +259,16 @@ module NATS
211
259
  @disconnect_cb = nil
212
260
  end
213
261
 
262
+ # Drain gracefully disconnects from the server, letting
263
+ # subscribers process pending messages already sent by server and
264
+ # optionally calls the associated block.
265
+ # @param [Block] &blk called when drain is done and connection is closed.
266
+ def drain(&blk)
267
+ if (client and !client.draining? and (client.connected? || client.reconnecting?))
268
+ client.drain { blk.call if blk }
269
+ end
270
+ end
271
+
214
272
  # @return [URI] Connected server
215
273
  def connected_server
216
274
  return nil unless client
@@ -229,6 +287,12 @@ module NATS
229
287
  client.reconnecting?
230
288
  end
231
289
 
290
+ # @return [Boolean] Draining state
291
+ def draining?
292
+ return false unless client
293
+ client.draining?
294
+ end
295
+
232
296
  # @return [Hash] Options
233
297
  def options
234
298
  return {} unless client
@@ -343,16 +407,48 @@ module NATS
343
407
  uri.host != 'localhost' && uri.host != '127.0.0.1'
344
408
  end
345
409
 
410
+ def process_uri(uris)
411
+ connect_uris = []
412
+ uris.split(',').each do |uri|
413
+ opts = {}
414
+
415
+ # Scheme
416
+ if uri.include?("://")
417
+ scheme, uri = uri.split("://")
418
+ opts[:scheme] = scheme
419
+ else
420
+ opts[:scheme] = 'nats'
421
+ end
422
+
423
+ # UserInfo
424
+ if uri.include?("@")
425
+ userinfo, endpoint = uri.split("@")
426
+ host, port = endpoint.split(":")
427
+ opts[:userinfo] = userinfo
428
+ else
429
+ host, port = uri.split(":")
430
+ end
431
+
432
+ # Host and Port
433
+ opts[:host] = host || "localhost"
434
+ opts[:port] = port || DEFAULT_PORT
435
+
436
+ connect_uris << URI::Generic.build(opts)
437
+ end
438
+
439
+ connect_uris
440
+ end
346
441
  end
347
442
 
348
443
  attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden, :pongs_received #:nodoc:
349
- attr_reader :closing, :reconnecting, :server_pool, :options, :server_info #:nodoc
444
+ attr_reader :closing, :reconnecting, :draining, :server_pool, :options, :server_info #:nodoc
350
445
  attr_reader :msgs_received, :msgs_sent, :bytes_received, :bytes_sent, :pings
351
446
  attr_reader :disconnect_cb, :close_cb
352
447
 
353
448
  alias :connected? :connected
354
449
  alias :closing? :closing
355
450
  alias :reconnecting? :reconnecting
451
+ alias :draining? :draining
356
452
 
357
453
  def initialize(options)
358
454
  @options = options
@@ -383,6 +479,9 @@ module NATS
383
479
  @resp_sub_prefix = nil
384
480
  @nuid = NATS::NUID.new
385
481
 
482
+ # Drain mode
483
+ @draining = false
484
+ @drained_subs = false
386
485
  send_connect_command
387
486
  end
388
487
 
@@ -392,7 +491,7 @@ module NATS
392
491
  # @param [String] opt_reply
393
492
  # @param [Block] blk, closure called when publish has been processed by the server.
394
493
  def publish(subject, msg=EMPTY_MSG, opt_reply=nil, &blk)
395
- return unless subject
494
+ return unless subject and not @drained_subs
396
495
  msg = msg.to_s
397
496
 
398
497
  # Accounting
@@ -412,7 +511,7 @@ module NATS
412
511
  # @param [Block] callback, called when a message is delivered.
413
512
  # @return [Object] sid, Subject Identifier
414
513
  def subscribe(subject, opts={}, &callback)
415
- return unless subject
514
+ return unless subject and not draining?
416
515
  sid = (@ssid += 1)
417
516
  sub = @subs[sid] = { :subject => subject, :callback => callback, :received => 0 }
418
517
  sub[:queue] = opts[:queue] if opts[:queue]
@@ -427,6 +526,7 @@ module NATS
427
526
  # @param [Object] sid
428
527
  # @param [Number] opt_max, optional number of responses to receive before auto-unsubscribing
429
528
  def unsubscribe(sid, opt_max=nil)
529
+ return if draining?
430
530
  opt_max_str = " #{opt_max}" unless opt_max.nil?
431
531
  send_command("UNSUB #{sid}#{opt_max_str}#{CR_LF}")
432
532
  return unless sub = @subs[sid]
@@ -434,6 +534,48 @@ module NATS
434
534
  @subs.delete(sid) unless (sub[:max] && (sub[:received] < sub[:max]))
435
535
  end
436
536
 
537
+ # Drain gracefully closes the connection.
538
+ # @param [Block] blk called when drain is done and connection is closed.
539
+ def drain(&blk)
540
+ return if draining? or closing?
541
+ @draining = true
542
+
543
+ # Remove interest in all subjects to stop receiving messages.
544
+ @subs.each do |sid, _|
545
+ send_command("UNSUB #{sid} #{CR_LF}")
546
+ end
547
+
548
+ # Roundtrip to ensure no more messages are received.
549
+ flush do
550
+ drain_timeout_timer, draining_timer = nil, nil
551
+ drain_timeout_timer = EM.add_timer(options[:drain_timeout]) do
552
+ EM.cancel_timer(draining_timer)
553
+
554
+ # Report the timeout via the error callback and just close
555
+ err_cb.call(NATS::ClientError.new("Drain Timeout"))
556
+ @draining = false
557
+ close unless closing?
558
+ blk.call if blk
559
+ end
560
+
561
+ # Periodically check for the pending data to be empty.
562
+ draining_timer = EM.add_periodic_timer(0.1) do
563
+ next unless closing? or @buf.nil? or @buf.empty?
564
+
565
+ # Subscriptions have been drained already so disallow publishing.
566
+ @drained_subs = true
567
+ next unless pending_data_size == 0
568
+ EM.cancel_timer(draining_timer)
569
+ EM.cancel_timer(drain_timeout_timer)
570
+
571
+ # We're done draining and can close now.
572
+ @draining = false
573
+ close unless closing?
574
+ blk.call if blk
575
+ end
576
+ end
577
+ end
578
+
437
579
  # Return the active subscription count.
438
580
  # @return [Number]
439
581
  def subscription_count
@@ -628,7 +770,7 @@ module NATS
628
770
  end
629
771
 
630
772
  def auth_connection?
631
- !@uri.user.nil?
773
+ !@uri.user.nil? || @options[:token]
632
774
  end
633
775
 
634
776
  def connect_command #:nodoc:
@@ -637,12 +779,19 @@ module NATS
637
779
  :pedantic => @options[:pedantic],
638
780
  :lang => ::NATS::LANG,
639
781
  :version => ::NATS::VERSION,
640
- :protocol => ::NATS::PROTOCOL_VERSION
782
+ :protocol => ::NATS::PROTOCOL_VERSION,
783
+ :echo => !@options[:no_echo]
641
784
  }
642
- if auth_connection?
785
+ case
786
+ when @options[:token]
787
+ cs[:auth_token] = @options[:token]
788
+ when @uri.password.nil?
789
+ cs[:auth_token] = @uri.user
790
+ else
643
791
  cs[:user] = @uri.user
644
792
  cs[:pass] = @uri.password
645
- end
793
+ end if auth_connection?
794
+
646
795
  cs[:name] = @options[:name] if @options[:name]
647
796
  cs[:ssl_required] = @ssl if @ssl
648
797
  cs[:tls_required] = true if @tls
@@ -701,6 +850,7 @@ module NATS
701
850
 
702
851
  def receive_data(data) #:nodoc:
703
852
  @buf = @buf ? @buf << data : data
853
+
704
854
  while (@buf)
705
855
  case @parse_state
706
856
  when AWAITING_CONTROL_LINE
@@ -748,7 +898,6 @@ module NATS
748
898
  @parse_state = AWAITING_CONTROL_LINE
749
899
  @buf = nil if (@buf && @buf.empty?)
750
900
  end
751
-
752
901
  end
753
902
  end
754
903
 
@@ -775,6 +924,14 @@ module NATS
775
924
  # Otherwise, use a regular connection.
776
925
  end
777
926
 
927
+ # Check whether there no echo is supported by the server.
928
+ if @options[:no_echo]
929
+ if @server_info[:proto].nil? || @server_info[:proto] < 1
930
+ err_cb.call(NATS::ServerError.new('No echo option not supported by this server'))
931
+ close_connection_after_writing
932
+ end
933
+ end
934
+
778
935
  # Detect any announced server that we might not be aware of...
779
936
  connect_urls = @server_info[:connect_urls]
780
937
  if connect_urls
@@ -792,9 +949,9 @@ module NATS
792
949
  u.password = options[:pass] if options[:pass]
793
950
 
794
951
  # Use creds from the current server if not set explicitly.
795
- if @uri
796
- u.user ||= @uri.user if @uri.user
797
- u.password ||= @uri.password if @uri.password
952
+ if @uri and !@uri.user.nil? and !@uri.user.empty?
953
+ u.user ||= @uri.user
954
+ u.password ||= @uri.password
798
955
  end
799
956
 
800
957
  srvs << { :uri => u, :reconnect_attempts => 0, :discovered => true }
@@ -14,7 +14,7 @@
14
14
 
15
15
  module NATS
16
16
  # NOTE: These are all announced to the server on CONNECT
17
- VERSION = "0.9.2".freeze
17
+ VERSION = "0.10.0".freeze
18
18
  LANG = RUBY_ENGINE
19
19
  PROTOCOL_VERSION = 1
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Collison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-27 00:00:00.000000000 Z
11
+ date: 2018-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine