nats 0.4.10 → 0.4.22

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ # HISTORY
2
+
3
+ ## v0.4.22 (Mar 5, 2012)
4
+
5
+ - HTTP based server monitoring (/varz, /connz, /healthz)
6
+ - Perfomance and Stability improvements
7
+ - Client monitoring
8
+ - Server to Client pings
9
+ - Multiple Auth users
10
+ - SSL/TSL support
11
+ - nats-top utility
12
+ - Connection state dump on SIGUSR2
13
+ - Client Server information support
14
+ - Client Fast Producer support
15
+ - Client reconenct callbacks
16
+ - Server Max Connections support
17
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.4.10...v0.4.22
18
+
19
+ ## v0.4.10 (Apr 21, 2011)
20
+
21
+ - Minor bug fixes
22
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.4.8...v0.4.10
23
+
24
+ ## v0.4.8 (Apr 2, 2011)
25
+
26
+ - Minor bug fixes
27
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.4.2...v0.4.8
28
+
29
+ ## v0.4.2 (Feb 21, 2011)
30
+
31
+ - Queue group support
32
+ - Auto-unsubscribe support
33
+ - Time expiration on subscriptions
34
+ - Jruby initial support
35
+ - Performance enhancements
36
+ - Complete config file support
37
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.3.12...v0.4.2
38
+
39
+ ## v0.3.12 (Nov 21, 2010)
40
+
41
+ - Initial Release
42
+
data/README.md CHANGED
@@ -1,102 +1,114 @@
1
1
  # NATS
2
2
 
3
- A lightweight EventMachine based publish-subscribe messaging system.
3
+ A lightweight publish-subscribe and distributed queueing messaging system.
4
+
5
+ [![Build Status](https://secure.travis-ci.org/derekcollison/nats.png)](http://travis-ci.org/derekcollison/nats)
4
6
 
5
7
  ## Supported Platforms
6
8
 
7
9
  This gem currently works on the following Ruby platforms:
8
10
 
9
- - MRI 1.8 and 1.9 (Performance is best on 1.9.2)
11
+ - MRI 1.8 and 1.9 (Performance is best on 1.9.3)
10
12
  - Rubinius
11
13
  - JRuby
12
14
 
15
+ There is a [Node.js](https://github.com/derekcollison/node_nats) client available as well.
16
+
13
17
  ## Getting Started
14
18
 
15
- [sudo] gem install nats
16
- == or ==
17
- [sudo] rake geminstall
19
+ ```bash
20
+ [sudo] gem install nats
21
+ == or ==
22
+ [sudo] rake geminstall
18
23
 
19
- nats-sub foo &
20
- nats-pub foo "Hello World!'
24
+ nats-sub foo &
25
+ nats-pub foo 'Hello World!'
26
+ ```
21
27
 
22
- ## Usage
28
+ ## Basic Usage
23
29
 
24
- require "nats/client"
30
+ ```ruby
31
+ require "nats/client"
25
32
 
26
- NATS.start do
33
+ NATS.start do
27
34
 
28
- # Simple Subscriber
29
- NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
35
+ # Simple Subscriber
36
+ NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
30
37
 
31
- # Simple Publisher
32
- NATS.publish('foo.bar.baz', 'Hello World!')
38
+ # Simple Publisher
39
+ NATS.publish('foo.bar.baz', 'Hello World!')
33
40
 
34
- # Unsubscribing
35
- sid = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
36
- NATS.unsubscribe(sid)
41
+ # Unsubscribing
42
+ sid = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
43
+ NATS.unsubscribe(sid)
37
44
 
38
- # Requests
39
- NATS.request('help') { |response| puts "Got a response: '#{response}'" }
45
+ # Requests
46
+ NATS.request('help') { |response| puts "Got a response: '#{response}'" }
40
47
 
41
- # Replies
42
- NATS.subscribe('help') { |msg, reply| NATS.publish(reply, "I'll help!") }
48
+ # Replies
49
+ NATS.subscribe('help') { |msg, reply| NATS.publish(reply, "I'll help!") }
43
50
 
44
- # Stop using NATS.stop, exits EM loop if NATS.start started the loop
45
- NATS.stop
51
+ # Stop using NATS.stop, exits EM loop if NATS.start started the loop
52
+ NATS.stop
46
53
 
47
- end
54
+ end
55
+ ```
48
56
 
49
57
  ## Wildcard Subscriptions
50
58
 
51
- # '*" matches any token, at any level of the subject.
52
- NATS.subscribe('foo.*.baz') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
53
- NATS.subscribe('foo.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
54
- NATS.subscribe('*.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
59
+ ```ruby
60
+ # "*" matches any token, at any level of the subject.
61
+ NATS.subscribe('foo.*.baz') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
62
+ NATS.subscribe('foo.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
63
+ NATS.subscribe('*.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
55
64
 
56
- # '>" matches any length of the tail of a subject and can only be last token
57
- # E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
58
- NATS.subscribe('foo.>') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
65
+ # ">" matches any length of the tail of a subject and can only be the last token
66
+ # E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
67
+ NATS.subscribe('foo.>') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
68
+ ```
59
69
 
60
70
  ## Queues Groups
61
71
 
62
- # All subscriptions with the same queue name will form a queue group
63
- # Each message will be delivered to only one subscriber per queue group, queuing semantics
64
- # You can have as many queue groups as you wish
65
- # Normal subscribers will continue to work as expected.
66
- NATS.subscribe(subject, :queue => 'job.workers') { |msg| puts "Received '#{msg}'" }
72
+ ```ruby
73
+ # All subscriptions with the same queue name will form a queue group
74
+ # Each message will be delivered to only one subscriber per queue group, queuing semantics
75
+ # You can have as many queue groups as you wish
76
+ # Normal subscribers will continue to work as expected.
77
+ NATS.subscribe(subject, :queue => 'job.workers') { |msg| puts "Received '#{msg}'" }
78
+ ```
67
79
 
68
80
  ## Advanced Usage
81
+ ```ruby
82
+ # Publish with closure, callback fires when server has processed the message
83
+ NATS.publish('foo', 'You done?') { puts 'msg processed!' }
69
84
 
70
- # Publish with closure, callback fires when server has processed the message
71
- NATS.publish('foo', 'You done?') { puts 'msg processed!' }
72
-
73
- # Timeouts for subscriptions
74
- sid = NATS.subscribe('foo') { received += 1 }
75
- NATS.timeout(sid, TIMEOUT_IN_SECS) { timeout_recvd = true }
76
-
77
- # Timeout unless a certain number of messages have been received
78
- NATS.timeout(sid, TIMEOUT_IN_SECS, :expected => 2) { timeout_recvd = true }
85
+ # Timeouts for subscriptions
86
+ sid = NATS.subscribe('foo') { received += 1 }
87
+ NATS.timeout(sid, TIMEOUT_IN_SECS) { timeout_recvd = true }
79
88
 
80
- # Auto-unsubscribe after MAX_WANTED messages received
81
- NATS.unsubscribe(sid, MAX_WANTED)
89
+ # Timeout unless a certain number of messages have been received
90
+ NATS.timeout(sid, TIMEOUT_IN_SECS, :expected => 2) { timeout_recvd = true }
82
91
 
83
- # Multiple connections
84
- NATS.subscribe('test') do |msg|
85
- puts "received msg"
86
- NATS.stop
87
- end
92
+ # Auto-unsubscribe after MAX_WANTED messages received
93
+ NATS.unsubscribe(sid, MAX_WANTED)
88
94
 
89
- # Form second connection to send message on
90
- NATS.connect { NATS.publish('test', 'Hello World!') }
95
+ # Multiple connections
96
+ NATS.subscribe('test') do |msg|
97
+ puts "received msg"
98
+ NATS.stop
99
+ end
91
100
 
101
+ # Form second connection to send message on
102
+ NATS.connect { NATS.publish('test', 'Hello World!') }
103
+ ```
92
104
 
93
- See examples and benchmark for more information..
105
+ See examples and benchmarks for more information..
94
106
 
95
107
  ## License
96
108
 
97
109
  (The MIT License)
98
110
 
99
- Copyright (c) 2010, 2011 Derek Collison
111
+ Copyright (c) 2010-2012 Derek Collison
100
112
 
101
113
  Permission is hereby granted, free of charge, to any person obtaining a copy
102
114
  of this software and associated documentation files (the "Software"), to
data/Rakefile CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  desc "Run rspec"
3
2
  task :spec do
4
3
  sh('bundle install')
@@ -7,6 +6,7 @@ task :spec do
7
6
  t.rspec_opts = %w(-fs -c)
8
7
  end
9
8
  end
9
+ task :default => :spec
10
10
 
11
11
  desc "Build the gem"
12
12
  task :gem do
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'rubygems'
5
+ require 'nats/client'
6
+
7
+ ['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
8
+
9
+ def usage
10
+ puts "Usage: nats-request <subject> <msg> [-s server] [-t] [-n responses]"; exit
11
+ end
12
+
13
+ args = ARGV.dup
14
+ opts_parser = OptionParser.new do |opts|
15
+ opts.on('-s SERVER') { |server| $nats_server = server }
16
+ opts.on('-t') { $show_time = true }
17
+ opts.on('-n RESPONSES') { |responses| $responses = Integer(responses) if Integer(responses) > 0 }
18
+ end
19
+ args = opts_parser.parse!(args)
20
+
21
+ subject, msg = args
22
+ usage unless subject
23
+ msg ||= 'Hello World'
24
+
25
+ def time_prefix
26
+ "[#{Time.now}] " if $show_time
27
+ end
28
+
29
+ def header
30
+ $i=0 unless $i
31
+ "#{time_prefix}[\##{$i+=1}]"
32
+ end
33
+
34
+ NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
35
+
36
+ NATS.start(:uri => $nats_server, :autostart => true) do
37
+ NATS.request(subject, msg) { |(msg, reply)|
38
+ puts "#{header} Replied with : '#{msg}'"
39
+ exit! if $responses && ($responses-=1) < 1
40
+ }
41
+ end
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'io/wait'
7
+
8
+ require 'rubygems'
9
+ require 'json'
10
+
11
+ def usage
12
+ puts "Usage: nats-top [-s server_uri] [-m local monitor port] [-n num_connections] [-d delay_secs] [--sort sort_by]"
13
+ puts "--sort_options for more help"
14
+ exit
15
+ end
16
+
17
+ $valid_sort_options = ['pending_size', 'msgs_to', 'msgs_from', 'bytes_to', 'bytes_from', 'subs']
18
+
19
+ def sort_options_help
20
+ puts "Available sort_by options: #{$valid_sort_options.join(', ')}."
21
+ puts "E.g. #{$0} -s bytes_to"
22
+ exit
23
+ end
24
+
25
+ args = ARGV.dup
26
+ opts_parser = OptionParser.new do |opts|
27
+ opts.on('-s server_uri') { |server| $nats_server = server }
28
+ opts.on('-m local_port') { |port| $nats_port = port.to_i }
29
+ opts.on('-n num_connections') { |num| $num_connections = num.to_i }
30
+ opts.on('-d delay') { |delay| $delay = delay.to_f }
31
+ opts.on('--sort sort_by') { |sort_key| $sort_key = sort_key }
32
+ opts.on('--sort_options') { sort_options_help }
33
+ opts.on('-h') { usage }
34
+ opts.on('--help') { usage }
35
+ end
36
+ args = opts_parser.parse!(args)
37
+
38
+ DEFAULT_MONITOR_PORT = 9222
39
+ DEFAULT_NUM_CONNECTIONS = 10
40
+ DEFAULT_DELAY = 1 #sec
41
+ DEFAULT_SORT = 'pending_size'
42
+
43
+ $nats_port = DEFAULT_MONITOR_PORT if $nats_port.nil?
44
+ $num_connections = DEFAULT_NUM_CONNECTIONS if $num_connections.nil?
45
+ $nats_server = "http://localhost:#{$nats_port}" if $nats_server.nil?
46
+
47
+ $nats_server = "http://#{$nats_server}" unless $nats_server.start_with?('http')
48
+
49
+ $delay = DEFAULT_DELAY if $delay.nil?
50
+ $sort_key = DEFAULT_SORT if $sort_key.nil?
51
+ $sort_key.downcase!
52
+
53
+ unless $valid_sort_options.include?($sort_key)
54
+ puts "Invalid sort_by argument: #{$sort_key}"
55
+ sort_options_help
56
+ end
57
+
58
+ varz_uri = URI.parse("#{$nats_server}/varz")
59
+ connz_uri = URI.parse("#{$nats_server}/connz?n=#{$num_connections}&s=#{$sort_key}")
60
+
61
+ def psize(size, prec=1)
62
+ return 'NA' unless size
63
+ return sprintf("%.#{prec}f", size) if size < 1024
64
+ return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
65
+ return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
66
+ return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
67
+ end
68
+
69
+ def clear_screen
70
+ print "\e[H\e[2J"
71
+ end
72
+
73
+ ['TERM', 'INT'].each { |s| trap(s) { clear_screen; exit! } }
74
+
75
+ in_last_msgs = in_last_bytes = 0
76
+ out_last_msgs = out_last_bytes = 0
77
+
78
+ poll = Time.now
79
+ first = true
80
+
81
+ while true
82
+ begin
83
+
84
+ varz_response = Net::HTTP::get_response(varz_uri)
85
+ varz = JSON.parse(varz_response.body, :symbolize_keys => true, :symbolize_names => true)
86
+
87
+ # Simple rates
88
+ delta_in_msgs, in_last_msgs = varz[:in_msgs] - in_last_msgs, varz[:in_msgs]
89
+ delta_in_bytes, in_last_bytes = varz[:in_bytes] - in_last_bytes, varz[:in_bytes]
90
+ delta_out_msgs, out_last_msgs = varz[:out_msgs] - out_last_msgs, varz[:out_msgs]
91
+ delta_out_bytes, out_last_bytes = varz[:out_bytes] - out_last_bytes, varz[:out_bytes]
92
+
93
+ now = Time.now
94
+ tdelta, poll = now - poll, now
95
+
96
+ unless first
97
+ rate_in_msgs = delta_in_msgs / tdelta
98
+ rate_in_bytes = delta_in_bytes / tdelta
99
+ rate_out_msgs = delta_out_msgs / tdelta
100
+ rate_out_bytes = delta_out_bytes / tdelta
101
+ end
102
+
103
+ connz_response = Net::HTTP::get_response(connz_uri)
104
+ connz = JSON.parse(connz_response.body, :symbolize_keys => true, :symbolize_names => true)
105
+
106
+ clear_screen
107
+
108
+ puts "\nServer:"
109
+ puts " Load: CPU: #{varz[:cpu]}% Memory: #{psize(varz[:mem])}"
110
+ print " In: Msgs: #{psize(varz[:in_msgs])} Bytes: #{psize(varz[:in_bytes])}"
111
+ puts " Msgs/Sec: #{psize(rate_in_msgs)} Bytes/Sec: #{psize(rate_in_bytes)}"
112
+
113
+ print " Out: Msgs: #{psize(varz[:out_msgs])} Bytes: #{psize(varz[:out_bytes])}"
114
+ puts " Msgs/Sec: #{psize(rate_out_msgs)} Bytes/Sec: #{psize(rate_out_bytes)}"
115
+
116
+ puts "\nConnections: #{psize(connz[:num_connections], 0)}"
117
+
118
+ conn_t = " %-20s %-8s %-6s %-10s %-10s %-10s %-10s %-10s\n"
119
+ printf(conn_t, 'HOST', 'CID', 'SUBS', 'PENDING', 'MSGS_TO', 'MSGS_FROM', 'BYTES_TO', 'BYTES_FROM')
120
+
121
+ connz[:connections].each do |conn|
122
+ printf(conn_t, "#{conn[:ip]}:#{conn[:port]}",
123
+ conn[:cid],
124
+ psize(conn[:subscriptions]),
125
+ psize(conn[:pending_size]),
126
+ psize(conn[:out_msgs]),
127
+ psize(conn[:in_msgs]),
128
+ psize(conn[:out_bytes]),
129
+ psize(conn[:in_bytes])
130
+ )
131
+ end
132
+ puts
133
+
134
+ first = false
135
+
136
+ sleep($delay)
137
+
138
+ rescue => e
139
+ puts "Error: #{e}"
140
+ exit(1)
141
+ end
142
+
143
+ end
144
+
145
+
146
+
@@ -8,7 +8,7 @@ require "#{ep}/ext/json"
8
8
 
9
9
  module NATS
10
10
 
11
- VERSION = "0.4.10".freeze
11
+ VERSION = '0.4.22'.freeze
12
12
 
13
13
  DEFAULT_PORT = 4222
14
14
  DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT}".freeze
@@ -16,6 +16,11 @@ module NATS
16
16
  MAX_RECONNECT_ATTEMPTS = 10
17
17
  RECONNECT_TIME_WAIT = 2
18
18
 
19
+ MAX_PENDING_SIZE = 32768
20
+
21
+ # Maximum outbound size per client to trigger FP, 20MB
22
+ FAST_PRODUCER_THRESHOLD = (10*1024*1024)
23
+
19
24
  # Protocol
20
25
  # @private
21
26
  MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i #:nodoc:
@@ -50,19 +55,20 @@ module NATS
50
55
  # Duplicate autostart protection
51
56
  @@tried_autostart = {}
52
57
 
53
- class Error < StandardError #:nodoc:
54
- end
58
+ class Error < StandardError; end #:nodoc:
55
59
 
56
60
  # When the NATS server sends us an ERROR message, this is raised/passed by default
57
- class ServerError < Error #:nodoc:
58
- end
61
+ class ServerError < Error; end #:nodoc:
62
+
63
+ # When we detect error on the client side (e.g. Fast Producer)
64
+ class ClientError < Error; end #:nodoc:
59
65
 
60
66
  # When we cannot connect to the server (either initially or after a reconnect), this is raised/passed
61
- class ConnectError < Error #:nodoc:
62
- end
67
+ class ConnectError < Error; end #:nodoc:
63
68
 
64
69
  class << self
65
70
  attr_reader :client, :reactor_was_running, :err_cb, :err_cb_overridden #:nodoc:
71
+ attr_reader :reconnect_cb #:nodoc
66
72
  attr_accessor :timeout_cb #:nodoc
67
73
 
68
74
  alias :reactor_was_running? :reactor_was_running
@@ -72,12 +78,15 @@ module NATS
72
78
  # The optional block will be called when the connection has been completed.
73
79
  #
74
80
  # @param [Hash] opts
75
- # @option opts [String] :uri The URI to connect to, example nats://localhost:4222
81
+ # @option opts [String|URI] :uri The URI to connect to, example nats://localhost:4222
76
82
  # @option opts [Boolean] :autostart Boolean that can be used to engage server autostart functionality.
77
83
  # @option opts [Boolean] :reconnect Boolean that can be used to suppress reconnect functionality.
78
84
  # @option opts [Boolean] :debug Boolean that can be used to output additional debug information.
79
85
  # @option opts [Boolean] :verbose Boolean that is sent to server for setting verbose protocol mode.
80
86
  # @option opts [Boolean] :pedantic Boolean that is sent to server for setting pedantic mode.
87
+ # @option opts [Boolean] :ssl Boolean that is sent to server for setting TLS/SSL mode.
88
+ # @option opts [Integer] :max_reconnect_attempts Integer that can be used to set the max number of reconnect tries
89
+ # @option opts [Integer] :reconnect_time_wait Integer that can be used to set the number of seconds to wait between reconnect tries
81
90
  # @param [Block] &blk called when the connection is completed. Connection will be passed to the block.
82
91
  # @return [NATS] connection to the server.
83
92
  def connect(opts={}, &blk)
@@ -85,14 +94,22 @@ module NATS
85
94
  opts[:verbose] = false if opts[:verbose].nil?
86
95
  opts[:pedantic] = false if opts[:pedantic].nil?
87
96
  opts[:reconnect] = true if opts[:reconnect].nil?
97
+ opts[:ssl] = false if opts[:ssl].nil?
98
+ opts[:max_reconnect_attempts] = MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
99
+ opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
88
100
 
89
101
  # Override with ENV
90
102
  opts[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
91
- opts[:verbose] = ENV['NATS_VERBOSE'] unless ENV['NATS_VERBOSE'].nil?
92
- opts[:pedantic] = ENV['NATS_PEDANTIC'] unless ENV['NATS_PEDANTIC'].nil?
93
- opts[:debug] = ENV['NATS_DEBUG'] if !ENV['NATS_DEBUG'].nil?
94
- @uri = opts[:uri] = URI.parse(opts[:uri])
95
- @err_cb = proc {|e| raise e } unless err_cb
103
+ opts[:verbose] = ENV['NATS_VERBOSE'].downcase == 'true' unless ENV['NATS_VERBOSE'].nil?
104
+ opts[:pedantic] = ENV['NATS_PEDANTIC'].downcase == 'true' unless ENV['NATS_PEDANTIC'].nil?
105
+ opts[:debug] = ENV['NATS_DEBUG'].downcase == 'true' unless ENV['NATS_DEBUG'].nil?
106
+ opts[:reconnect] = ENV['NATS_RECONNECT'].downcase == 'true' unless ENV['NATS_RECONNECT'].nil?
107
+ opts[:fast_producer_error] = ENV['NATS_FAST_PRODUCER'].downcase == 'true' unless ENV['NATS_FAST_PRODUCER'].nil?
108
+ opts[:ssl] = ENV['NATS_SSL'].downcase == 'true' unless ENV['NATS_SSL'].nil?
109
+ opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
110
+ opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
111
+ @uri = opts[:uri] = opts[:uri].is_a?(URI) ? opts[:uri] : URI.parse(opts[:uri])
112
+ @err_cb = proc { |e| raise e } unless err_cb
96
113
  check_autostart(@uri) if opts[:autostart] == true
97
114
 
98
115
  client = EM.connect(@uri.host, @uri.port, self, opts)
@@ -115,7 +132,7 @@ module NATS
115
132
  # Close the default client connection and optionally call the associated block.
116
133
  # @param [Block] &blk called when the connection is closed.
117
134
  def stop(&blk)
118
- client.close if (client and client.connected?)
135
+ client.close if (client and (client.connected? || client.reconnecting?))
119
136
  blk.call if blk
120
137
  @@tried_autostart = {}
121
138
  @err_cb = nil
@@ -127,18 +144,36 @@ module NATS
127
144
  client.connected?
128
145
  end
129
146
 
147
+ # @return [Boolean] Reconnecting state
148
+ def reconnecting?
149
+ return false unless client
150
+ client.reconnecting?
151
+ end
152
+
130
153
  # @return [Hash] Options
131
154
  def options
132
155
  return {} unless client
133
156
  client.options
134
157
  end
135
158
 
159
+ # @return [Hash] Server information
160
+ def server_info
161
+ return nil unless client
162
+ client.server_info
163
+ end
164
+
136
165
  # Set the default on_error callback.
137
166
  # @param [Block] &callback called when an error has been detected.
138
167
  def on_error(&callback)
139
168
  @err_cb, @err_cb_overridden = callback, true
140
169
  end
141
170
 
171
+ # Set the default on_reconnect callback.
172
+ # @param [Block] &callback called when a reconnect attempt is made.
173
+ def on_reconnect(&callback)
174
+ @reconnect_cb = callback
175
+ end
176
+
142
177
  # Publish a message using the default client connection.
143
178
  # @see NATS#publish
144
179
  def publish(*args, &blk)
@@ -177,6 +212,18 @@ module NATS
177
212
  "_INBOX.%04x%04x%04x%04x%04x%06x" % v
178
213
  end
179
214
 
215
+ # Flushes all messages and subscriptions in the default connection
216
+ # @see NATS#flush
217
+ def flush(*args, &blk)
218
+ (@client ||= connect).flush(*args, &blk)
219
+ end
220
+
221
+ # Return bytes outstanding for the default client connection.
222
+ # @see NATS#pending_data_size
223
+ def pending_data_size(*args)
224
+ (@client ||= connect).pending_data_size(*args)
225
+ end
226
+
180
227
  def wait_for_server(uri, max_wait = 5) # :nodoc:
181
228
  start = Time.now
182
229
  while (Time.now - start < max_wait) # Wait max_wait seconds max
@@ -194,6 +241,10 @@ module NATS
194
241
  return false
195
242
  end
196
243
 
244
+ def clear_client # :nodoc:
245
+ @client = nil
246
+ end
247
+
197
248
  private
198
249
 
199
250
  def check_autostart(uri)
@@ -222,7 +273,8 @@ module NATS
222
273
  end
223
274
 
224
275
  attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden #:nodoc:
225
- attr_reader :closing, :reconnecting, :options #:nodoc
276
+ attr_reader :closing, :reconnecting, :options, :server_info #:nodoc
277
+ attr_reader :msgs_received, :msgs_sent, :bytes_received, :bytes_sent, :pings
226
278
 
227
279
  alias :connected? :connected
228
280
  alias :closing? :closing
@@ -232,11 +284,15 @@ module NATS
232
284
  @uri = options[:uri]
233
285
  @uri.user = options[:user] if options[:user]
234
286
  @uri.password = options[:pass] if options[:pass]
287
+ @ssl = options[:ssl] if options[:ssl]
235
288
  @options = options
236
289
  @ssid, @subs = 1, {}
237
290
  @err_cb = NATS.err_cb
238
291
  @reconnect_timer, @needed = nil, nil
292
+ @reconnect_cb = NATS.reconnect_cb
239
293
  @connected, @closing, @reconnecting = false, false, false
294
+ @msgs_received = @msgs_sent = @bytes_received = @bytes_sent = @pings = 0
295
+ @pending_size = 0
240
296
  send_connect_command
241
297
  end
242
298
 
@@ -248,6 +304,11 @@ module NATS
248
304
  def publish(subject, msg=EMPTY_MSG, opt_reply=nil, &blk)
249
305
  return unless subject
250
306
  msg = msg.to_s
307
+
308
+ # Accounting
309
+ @msgs_sent += 1
310
+ @bytes_sent += msg.bytesize if msg
311
+
251
312
  send_command("PUB #{subject} #{opt_reply} #{msg.bytesize}#{CR_LF}#{msg}#{CR_LF}")
252
313
  queue_server_rt(&blk) if blk
253
314
  end
@@ -283,6 +344,12 @@ module NATS
283
344
  @subs.delete(sid) unless (sub[:max] && (sub[:received] < sub[:max]))
284
345
  end
285
346
 
347
+ # Return the active subscription count.
348
+ # @return [Number]
349
+ def subscription_count
350
+ @subs.size
351
+ end
352
+
286
353
  # Setup a timeout for receiving messages for the subscription.
287
354
  # @param [Object] sid
288
355
  # @param [Number] timeout, float in seconds
@@ -323,6 +390,13 @@ module NATS
323
390
  return s
324
391
  end
325
392
 
393
+ # Flushes all messages and subscriptions for the connection.
394
+ # All messages and subscriptions have been processed by the server
395
+ # when the optional callback is called.
396
+ def flush(&blk)
397
+ queue_server_rt(&blk) if blk
398
+ end
399
+
326
400
  # Define a callback to be called when the client connection has been established.
327
401
  # @param [Block] callback
328
402
  def on_connect(&callback)
@@ -335,7 +409,7 @@ module NATS
335
409
  @err_cb, @err_cb_overridden = callback, true
336
410
  end
337
411
 
338
- # Define a callback to be called when a reconnect attempt is being made.
412
+ # Define a callback to be called when a reconnect attempt is made.
339
413
  # @param [Block] &blk called when the connection is closed.
340
414
  def on_reconnect(&callback)
341
415
  @reconnect_cb = callback
@@ -344,7 +418,14 @@ module NATS
344
418
  # Close the connection to the server.
345
419
  def close
346
420
  @closing = true
347
- close_connection_after_writing
421
+ EM.cancel_timer(@reconnect_timer) if @reconnect_timer
422
+ close_connection_after_writing if connected?
423
+ process_disconnect if reconnecting?
424
+ end
425
+
426
+ # Return bytes outstanding waiting to be sent to server.
427
+ def pending_data_size
428
+ get_outbound_data_size + @pending_size
348
429
  end
349
430
 
350
431
  def user_err_cb? # :nodoc:
@@ -357,6 +438,7 @@ module NATS
357
438
  cs[:user] = @uri.user
358
439
  cs[:pass] = @uri.password
359
440
  end
441
+ cs[:ssl_required] = @ssl if @ssl
360
442
  send_command("CONNECT #{cs.to_json}#{CR_LF}")
361
443
  end
362
444
 
@@ -367,10 +449,22 @@ module NATS
367
449
  end
368
450
 
369
451
  def on_msg(subject, sid, reply, msg) #:nodoc:
452
+
453
+ # Accounting - We should account for inbound even if they are not processed.
454
+ @msgs_received += 1
455
+ @bytes_received += msg.bytesize if msg
456
+
370
457
  return unless sub = @subs[sid]
371
458
 
372
459
  # Check for auto_unsubscribe
373
460
  sub[:received] += 1
461
+ if sub[:max]
462
+ # Client side support in case server did not receive unsubscribe
463
+ return unsubscribe(sid) if (sub[:received] > sub[:max])
464
+ # cleanup here if we have hit the max..
465
+ @subs.delete(sid) if (sub[:received] == sub[:max])
466
+ end
467
+
374
468
  return unsubscribe(sid) if (sub[:max] && (sub[:received] > sub[:max]))
375
469
 
376
470
  if cb = sub[:callback]
@@ -391,70 +485,86 @@ module NATS
391
485
 
392
486
  def flush_pending #:nodoc:
393
487
  return unless @pending
394
- @pending.each { |p| send_data(p) }
395
- @pending = nil
488
+ send_data(@pending.join)
489
+ @pending, @pending_size = nil, 0
396
490
  end
397
491
 
398
492
  def receive_data(data) #:nodoc:
399
493
  @buf = @buf ? @buf << data : data
400
494
  while (@buf)
401
- case @parse_state
402
-
403
- when AWAITING_CONTROL_LINE
404
- case @buf
405
- when MSG
406
- @buf = $'
407
- @sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
408
- @parse_state = AWAITING_MSG_PAYLOAD
409
- when OK # No-op right now
410
- @buf = $'
411
- when ERR
412
- @buf = $'
413
- err_cb.call(NATS::ServerError.new($1))
414
- when PING
415
- @buf = $'
416
- send_command(PONG_RESPONSE)
417
- when PONG
418
- @buf = $'
419
- cb = @pongs.shift
420
- cb.call if cb
421
- when INFO
422
- @buf = $'
423
- process_info($1)
424
- when UNKNOWN
425
- @buf = $'
426
- err_cb.call(NATS::Error.new("Unknown protocol: $1"))
427
- else
428
- # If we are here we do not have a complete line yet that we understand.
429
- return
430
- end
431
- @buf = nil if (@buf && @buf.empty?)
432
-
433
- when AWAITING_MSG_PAYLOAD
434
- return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
435
- on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
436
- @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
437
- @sub = @sid = @reply = @needed = nil
438
- @parse_state = AWAITING_CONTROL_LINE
439
- @buf = nil if (@buf && @buf.empty?)
495
+ case @parse_state
496
+ when AWAITING_CONTROL_LINE
497
+ case @buf
498
+ when MSG
499
+ @buf = $'
500
+ @sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
501
+ @parse_state = AWAITING_MSG_PAYLOAD
502
+ when OK # No-op right now
503
+ @buf = $'
504
+ when ERR
505
+ @buf = $'
506
+ err_cb.call(NATS::ServerError.new($1))
507
+ when PING
508
+ @pings += 1
509
+ @buf = $'
510
+ send_command(PONG_RESPONSE)
511
+ when PONG
512
+ @buf = $'
513
+ cb = @pongs.shift
514
+ cb.call if cb
515
+ when INFO
516
+ @buf = $'
517
+ process_info($1)
518
+ when UNKNOWN
519
+ @buf = $'
520
+ err_cb.call(NATS::ServerError.new("Unknown protocol: $1"))
521
+ else
522
+ # If we are here we do not have a complete line yet that we understand.
523
+ return
440
524
  end
525
+ @buf = nil if (@buf && @buf.empty?)
526
+
527
+ when AWAITING_MSG_PAYLOAD
528
+ return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
529
+ on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
530
+ @buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
531
+ @sub = @sid = @reply = @needed = nil
532
+ @parse_state = AWAITING_CONTROL_LINE
533
+ @buf = nil if (@buf && @buf.empty?)
534
+ end
535
+
441
536
  end
442
537
  end
443
538
 
444
539
  def process_info(info) #:nodoc:
445
540
  @server_info = JSON.parse(info, :symbolize_keys => true, :symbolize_names => true)
541
+ if @server_info[:ssl_required] && @ssl
542
+ start_tls
543
+ else
544
+ if @server_info[:ssl_required]
545
+ err_cb.call(NATS::ClientError.new('TLS/SSL required by server'))
546
+ elsif @ssl
547
+ err_cb.call(NATS::ClientError.new('TLS/SSL not supported by server'))
548
+ end
549
+ end
550
+ @server_info
446
551
  end
447
552
 
448
- def connection_completed #:nodoc:
553
+ def ssl_handshake_completed
449
554
  @connected = true
555
+ flush_pending
556
+ end
557
+
558
+ def connection_completed #:nodoc:
559
+ @connected = true unless @ssl
450
560
  if reconnecting?
451
561
  EM.cancel_timer(@reconnect_timer)
452
562
  send_connect_command
453
563
  @subs.each_pair { |k, v| send_command("SUB #{v[:subject]} #{k}#{CR_LF}") }
454
564
  end
455
- flush_pending if @pending
565
+ flush_pending unless @ssl
456
566
  unless user_err_cb? or reconnecting?
457
- @err_cb = proc {|e| raise e }
567
+ @err_cb = proc { |e| raise e }
458
568
  end
459
569
  if (connect_cb and not reconnecting?)
460
570
  # We will round trip the server here to make sure all state from any pending commands
@@ -468,43 +578,49 @@ module NATS
468
578
  def schedule_reconnect(wait=RECONNECT_TIME_WAIT) #:nodoc:
469
579
  @reconnecting = true
470
580
  @reconnect_attempts = 0
581
+ @connected = false
471
582
  @reconnect_timer = EM.add_periodic_timer(wait) { attempt_reconnect }
472
583
  end
473
584
 
474
585
  def unbind #:nodoc:
475
586
  if connected? and not closing? and not reconnecting? and @options[:reconnect]
476
- schedule_reconnect
587
+ schedule_reconnect(@options[:reconnect_time_wait])
477
588
  else
478
589
  process_disconnect unless reconnecting?
479
590
  end
480
591
  end
481
592
 
593
+ def disconnect_error_string
594
+ return "Client disconnected from server on #{@uri}." if @connected
595
+ return "Could not connect to server on #{@uri}"
596
+ end
597
+
482
598
  def process_disconnect #:nodoc:
483
- if not closing? and @err_cb
484
- err_string = @connected ? "Client disconnected from server on #{@uri}." : "Could not connect to server on #{@uri}"
485
- err_cb.call(NATS::ConnectError.new(err_string))
486
- end
599
+ err_cb.call(NATS::ConnectError.new(disconnect_error_string)) if not closing? and @err_cb
487
600
  ensure
488
601
  EM.cancel_timer(@reconnect_timer) if @reconnect_timer
489
- if (NATS.client == self and connected? and closing? and not NATS.reactor_was_running?)
490
- EM.stop
602
+ if (NATS.client == self)
603
+ NATS.clear_client
604
+ EM.stop if ((connected? || reconnecting?) and closing? and not NATS.reactor_was_running?)
491
605
  end
492
606
  @connected = @reconnecting = false
493
607
  true # Chaining
494
608
  end
495
609
 
496
610
  def attempt_reconnect #:nodoc:
497
- process_disconnect and return if (@reconnect_attempts += 1) > MAX_RECONNECT_ATTEMPTS
611
+ process_disconnect and return if (@reconnect_attempts += 1) > @options[:max_reconnect_attempts]
498
612
  EM.reconnect(@uri.host, @uri.port, self)
613
+ @reconnect_cb.call unless @reconnect_cb.nil?
499
614
  end
500
615
 
501
616
  def send_command(command) #:nodoc:
502
- queue_command(command) and return unless connected?
503
- send_data(command)
504
- end
505
-
506
- def queue_command(command) #:nodoc:
617
+ EM.next_tick { flush_pending } if (connected? && @pending.nil?)
507
618
  (@pending ||= []) << command
619
+ @pending_size += command.bytesize
620
+ flush_pending if (connected? && @pending_size > MAX_PENDING_SIZE)
621
+ if (@options[:fast_producer_error] && pending_data_size > FAST_PRODUCER_THRESHOLD)
622
+ err_cb.call(NATS::ClientError.new("Fast Producer: #{pending_data_size} bytes outstanding"))
623
+ end
508
624
  true
509
625
  end
510
626