nats 0.4.10 → 0.4.22
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.
- data/HISTORY.md +42 -0
- data/README.md +68 -56
- data/Rakefile +1 -1
- data/bin/nats-request +41 -0
- data/bin/nats-top +146 -0
- data/lib/nats/client.rb +190 -74
- data/lib/nats/server.rb +15 -0
- data/lib/nats/server/connection.rb +269 -0
- data/lib/nats/server/connz.rb +54 -0
- data/lib/nats/server/const.rb +24 -2
- data/lib/nats/server/options.rb +64 -3
- data/lib/nats/server/server.rb +73 -172
- data/lib/nats/server/sublist.rb +67 -16
- data/lib/nats/server/util.rb +45 -2
- data/lib/nats/server/varz.rb +35 -0
- data/nats.gemspec +12 -6
- metadata +64 -55
- data/ChangeLog +0 -11
data/HISTORY.md
ADDED
@@ -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
|
3
|
+
A lightweight publish-subscribe and distributed queueing messaging system.
|
4
|
+
|
5
|
+
[](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.
|
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
|
-
|
16
|
-
|
17
|
-
|
19
|
+
```bash
|
20
|
+
[sudo] gem install nats
|
21
|
+
== or ==
|
22
|
+
[sudo] rake geminstall
|
18
23
|
|
19
|
-
|
20
|
-
|
24
|
+
nats-sub foo &
|
25
|
+
nats-pub foo 'Hello World!'
|
26
|
+
```
|
21
27
|
|
22
|
-
## Usage
|
28
|
+
## Basic Usage
|
23
29
|
|
24
|
-
|
30
|
+
```ruby
|
31
|
+
require "nats/client"
|
25
32
|
|
26
|
-
|
33
|
+
NATS.start do
|
27
34
|
|
28
|
-
|
29
|
-
|
35
|
+
# Simple Subscriber
|
36
|
+
NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
|
30
37
|
|
31
|
-
|
32
|
-
|
38
|
+
# Simple Publisher
|
39
|
+
NATS.publish('foo.bar.baz', 'Hello World!')
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
# Unsubscribing
|
42
|
+
sid = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
|
43
|
+
NATS.unsubscribe(sid)
|
37
44
|
|
38
|
-
|
39
|
-
|
45
|
+
# Requests
|
46
|
+
NATS.request('help') { |response| puts "Got a response: '#{response}'" }
|
40
47
|
|
41
|
-
|
42
|
-
|
48
|
+
# Replies
|
49
|
+
NATS.subscribe('help') { |msg, reply| NATS.publish(reply, "I'll help!") }
|
43
50
|
|
44
|
-
|
45
|
-
|
51
|
+
# Stop using NATS.stop, exits EM loop if NATS.start started the loop
|
52
|
+
NATS.stop
|
46
53
|
|
47
|
-
|
54
|
+
end
|
55
|
+
```
|
48
56
|
|
49
57
|
## Wildcard Subscriptions
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
84
|
-
|
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
|
-
|
90
|
-
|
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
|
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
|
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
data/bin/nats-request
ADDED
@@ -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
|
data/bin/nats-top
ADDED
@@ -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
|
+
|
data/lib/nats/client.rb
CHANGED
@@ -8,7 +8,7 @@ require "#{ep}/ext/json"
|
|
8
8
|
|
9
9
|
module NATS
|
10
10
|
|
11
|
-
VERSION =
|
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
|
-
|
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']
|
94
|
-
|
95
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
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
|
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
|
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
|
490
|
-
|
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) >
|
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
|
-
|
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
|
|